File: //lib/python3.9/site-packages/ansible_collections/dellemc/powerflex/plugins/modules/volume.py
# 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 volumes on Dell Technologies (Dell) PowerFlex"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
module: volume
version_added: '1.0.0'
short_description: Manage volumes on Dell PowerFlex
description:
- Managing volumes on PowerFlex storage system includes
creating, getting details, modifying attributes and deleting volume.
- It also includes adding/removing snapshot policy,
mapping/unmapping volume to/from SDC and listing
associated snapshots.
author:
- P Srinivas Rao (@srinivas-rao5) <[email protected]>
extends_documentation_fragment:
- dellemc.powerflex.powerflex
options:
vol_name:
description:
- The name of the volume.
- Mandatory for create operation.
- It is unique across the PowerFlex array.
- Mutually exclusive with I(vol_id).
type: str
vol_id:
description:
- The ID of the volume.
- Except create operation, all other operations can be performed
using I(vol_id).
- Mutually exclusive with I(vol_name).
type: str
storage_pool_name:
description:
- The name of the storage pool.
- Either name or the id of the storage pool is required for creating a
volume.
- During creation, if storage pool name is provided then either
protection domain name or id must be mentioned along with it.
- Mutually exclusive with I(storage_pool_id).
type: str
storage_pool_id:
description:
- The ID of the storage pool.
- Either name or the id of the storage pool is required for creating
a volume.
- Mutually exclusive with I(storage_pool_name).
type: str
protection_domain_name:
description:
- The name of the protection domain.
- During creation of a volume, if more than one storage pool exists with
the same name then either protection domain name or id must be
mentioned along with it.
- Mutually exclusive with I(protection_domain_id).
type: str
protection_domain_id:
description:
- The ID of the protection domain.
- During creation of a volume, if more than one storage pool exists with
the same name then either protection domain name or id must be
mentioned along with it.
- Mutually exclusive with I(protection_domain_name).
type: str
vol_type:
description:
- Type of volume provisioning.
choices: ["THICK_PROVISIONED", "THIN_PROVISIONED"]
type: str
compression_type:
description:
- Type of the compression method.
choices: ["NORMAL", "NONE"]
type: str
use_rmcache:
description:
- Whether to use RM Cache or not.
type: bool
snapshot_policy_name:
description:
- Name of the snapshot policy.
- To remove/detach snapshot policy, empty
I(snapshot_policy_id)/I(snapshot_policy_name) is to be passed along with
I(auto_snap_remove_type).
type: str
snapshot_policy_id:
description:
- ID of the snapshot policy.
- To remove/detach snapshot policy, empty
I(snapshot_policy_id)/I(snapshot_policy_name) is to be passed along with
I(auto_snap_remove_type).
type: str
auto_snap_remove_type:
description:
- Whether to remove or detach the snapshot policy.
- To remove/detach snapshot policy, empty
I(snapshot_policy_id)/I(snapshot_policy_name) is to be passed along with
I(auto_snap_remove_type).
- If the snapshot policy name/id is passed empty then
I(auto_snap_remove_type) is defaulted to C(detach).
choices: ['remove', 'detach']
type: str
size:
description:
- The size of the volume.
- Size of the volume will be assigned as higher multiple of 8 GB.
type: int
cap_unit:
description:
- The unit of the volume size. It defaults to 'GB'.
choices: ['GB' , 'TB']
type: str
vol_new_name:
description:
- New name of the volume. Used to rename the volume.
type: str
allow_multiple_mappings:
description:
- Specifies whether to allow or not allow multiple mappings.
- If the volume is mapped to one SDC then for every new mapping
I(allow_multiple_mappings) has to be passed as True.
type: bool
sdc:
description:
- Specifies SDC parameters.
type: list
elements: dict
suboptions:
sdc_name:
description:
- Name of the SDC.
- Specify either I(sdc_name), I(sdc_id) or I(sdc_ip).
- 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).
- 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).
- Mutually exclusive with I(sdc_id) and I(sdc_ip).
type: str
access_mode:
description:
- Define the access mode for all mappings of the volume.
choices: ['READ_WRITE', 'READ_ONLY', 'NO_ACCESS']
type: str
bandwidth_limit:
description:
- Limit of volume network bandwidth.
- Need to mention in multiple of 1024 Kbps.
- To set no limit, 0 is to be passed.
type: int
iops_limit:
description:
- Limit of volume IOPS.
- Minimum IOPS limit is 11 and specify 0 for unlimited iops.
type: int
sdc_state:
description:
- Mapping state of the SDC.
choices: ['mapped', 'unmapped']
type: str
delete_snapshots:
description:
- If C(True), the volume and all its dependent snapshots will be deleted.
- If C(False), only the volume will be deleted.
- It can be specified only when the I(state) is C(absent).
- It defaults to C(False), if not specified.
type: bool
state:
description:
- State of the volume.
choices: ['present', 'absent']
required: true
type: str
notes:
- The I(check_mode) is not supported.
'''
EXAMPLES = r'''
- name: Create a volume
dellemc.powerflex.volume:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
vol_name: "sample_volume"
storage_pool_name: "pool_1"
protection_domain_name: "pd_1"
vol_type: "THICK_PROVISIONED"
compression_type: "NORMAL"
use_rmcache: True
size: 16
state: "present"
- name: Map a SDC to volume
dellemc.powerflex.volume:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
vol_name: "sample_volume"
allow_multiple_mappings: True
sdc:
- sdc_id: "92A304DB-EFD7-44DF-A07E-D78134CC9764"
access_mode: "READ_WRITE"
sdc_state: "mapped"
state: "present"
- name: Unmap a SDC to volume
dellemc.powerflex.volume:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
vol_name: "sample_volume"
sdc:
- sdc_id: "92A304DB-EFD7-44DF-A07E-D78134CC9764"
sdc_state: "unmapped"
state: "present"
- name: Map multiple SDCs to a volume
dellemc.powerflex.volume:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
vol_name: "sample_volume"
protection_domain_name: "pd_1"
sdc:
- sdc_id: "92A304DB-EFD7-44DF-A07E-D78134CC9764"
access_mode: "READ_WRITE"
bandwidth_limit: 2048
iops_limit: 20
- sdc_ip: "198.10.xxx.xxx"
access_mode: "READ_ONLY"
allow_multiple_mappings: True
sdc_state: "mapped"
state: "present"
- name: Get the details of the volume
dellemc.powerflex.volume:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
vol_id: "fe6c8b7100000005"
state: "present"
- name: Modify the details of the Volume
dellemc.powerflex.volume:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
vol_name: "sample_volume"
storage_pool_name: "pool_1"
new_vol_name: "new_sample_volume"
size: 64
state: "present"
- name: Delete the Volume
dellemc.powerflex.volume:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
vol_name: "sample_volume"
delete_snapshots: False
state: "absent"
- name: Delete the Volume and all its dependent snapshots
dellemc.powerflex.volume:
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
validate_certs: "{{validate_certs}}"
port: "{{port}}"
vol_name: "sample_volume"
delete_snapshots: True
state: "absent"
'''
RETURN = r'''
changed:
description: Whether or not the resource has changed.
returned: always
type: bool
sample: 'false'
volume_details:
description: Details of the volume.
returned: When volume exists
type: dict
contains:
id:
description: The ID of the volume.
type: str
mappedSdcInfo:
description: The details of the mapped SDC.
type: dict
contains:
sdcId:
description: ID of the SDC.
type: str
sdcName:
description: Name of the SDC.
type: str
sdcIp:
description: IP of the SDC.
type: str
accessMode:
description: Mapping access mode for the specified volume.
type: str
limitIops:
description: IOPS limit for the SDC.
type: int
limitBwInMbps:
description: Bandwidth limit for the SDC.
type: int
name:
description: Name of the volume.
type: str
sizeInKb:
description: Size of the volume in Kb.
type: int
sizeInGb:
description: Size of the volume in Gb.
type: int
storagePoolId:
description: ID of the storage pool in which volume resides.
type: str
storagePoolName:
description: Name of the storage pool in which volume resides.
type: str
protectionDomainId:
description: ID of the protection domain in which volume resides.
type: str
protectionDomainName:
description: Name of the protection domain in which volume resides.
type: str
snapshotPolicyId:
description: ID of the snapshot policy associated with volume.
type: str
snapshotPolicyName:
description: Name of the snapshot policy associated with volume.
type: str
snapshotsList:
description: List of snapshots associated with the volume.
type: str
"statistics":
description: Statistics details of the storage pool.
type: dict
contains:
"numOfChildVolumes":
description: Number of child volumes.
type: int
"numOfMappedSdcs":
description: Number of mapped Sdcs of the volume.
type: int
sample: {
"accessModeLimit": "ReadWrite",
"ancestorVolumeId": null,
"autoSnapshotGroupId": null,
"compressionMethod": "Invalid",
"consistencyGroupId": null,
"creationTime": 1631618520,
"dataLayout": "MediumGranularity",
"id": "cdd883cf00000002",
"links": [
{
"href": "/api/instances/Volume::cdd883cf00000002",
"rel": "self"
},
{
"href": "/api/instances/Volume::cdd883cf00000002/relationships
/Statistics",
"rel": "/api/Volume/relationship/Statistics"
},
{
"href": "/api/instances/VTree::6e86255c00000001",
"rel": "/api/parent/relationship/vtreeId"
},
{
"href": "/api/instances/StoragePool::e0d8f6c900000000",
"rel": "/api/parent/relationship/storagePoolId"
}
],
"lockedAutoSnapshot": false,
"lockedAutoSnapshotMarkedForRemoval": false,
"managedBy": "ScaleIO",
"mappedSdcInfo": null,
"name": "ansible-volume-1",
"notGenuineSnapshot": false,
"originalExpiryTime": 0,
"pairIds": null,
"protectionDomainId": "9300c1f900000000",
"protectionDomainName": "domain1",
"replicationJournalVolume": false,
"replicationTimeStamp": 0,
"retentionLevels": [],
"secureSnapshotExpTime": 0,
"sizeInGB": 16,
"sizeInKb": 16777216,
"snapshotPolicyId": null,
"snapshotPolicyName": null,
"snapshotsList": [
{
"accessModeLimit": "ReadOnly",
"ancestorVolumeId": "cdd883cf00000002",
"autoSnapshotGroupId": null,
"compressionMethod": "Invalid",
"consistencyGroupId": "22f1e80c00000001",
"creationTime": 1631619229,
"dataLayout": "MediumGranularity",
"id": "cdd883d000000004",
"links": [
{
"href": "/api/instances/Volume::cdd883d000000004",
"rel": "self"
},
{
"href": "/api/instances/Volume::cdd883d000000004
/relationships/Statistics",
"rel": "/api/Volume/relationship/Statistics"
},
{
"href": "/api/instances/Volume::cdd883cf00000002",
"rel": "/api/parent/relationship/ancestorVolumeId"
},
{
"href": "/api/instances/VTree::6e86255c00000001",
"rel": "/api/parent/relationship/vtreeId"
},
{
"href": "/api/instances/StoragePool::e0d8f6c900000000",
"rel": "/api/parent/relationship/storagePoolId"
}
],
"lockedAutoSnapshot": false,
"lockedAutoSnapshotMarkedForRemoval": false,
"managedBy": "ScaleIO",
"mappedSdcInfo": null,
"name": "ansible_vol_snap_1",
"notGenuineSnapshot": false,
"originalExpiryTime": 0,
"pairIds": null,
"replicationJournalVolume": false,
"replicationTimeStamp": 0,
"retentionLevels": [],
"secureSnapshotExpTime": 0,
"sizeInKb": 16777216,
"snplIdOfAutoSnapshot": null,
"snplIdOfSourceVolume": null,
"storagePoolId": "e0d8f6c900000000",
"timeStampIsAccurate": false,
"useRmcache": false,
"volumeReplicationState": "UnmarkedForReplication",
"volumeType": "Snapshot",
"vtreeId": "6e86255c00000001"
}
],
"statistics": {
"childVolumeIds": [
],
"descendantVolumeIds": [
],
"initiatorSdcId": null,
"mappedSdcIds": [
"c42425XXXXXX"
],
"numOfChildVolumes": 0,
"numOfDescendantVolumes": 0,
"numOfMappedSdcs": 1,
"registrationKey": null,
"registrationKeys": [
],
"replicationJournalVolume": false,
"replicationState": "UnmarkedForReplication",
"reservationType": "NotReserved",
"rplTotalJournalCap": 0,
"rplUsedJournalCap": 0,
"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
}
},
"snplIdOfAutoSnapshot": null,
"snplIdOfSourceVolume": null,
"storagePoolId": "e0d8f6c900000000",
"storagePoolName": "pool1",
"timeStampIsAccurate": false,
"useRmcache": false,
"volumeReplicationState": "UnmarkedForReplication",
"volumeType": "ThinProvisioned",
"vtreeId": "6e86255c00000001"
}
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.dellemc.powerflex.plugins.module_utils.storage.dell\
import utils
import copy
LOG = utils.get_logger('volume')
class PowerFlexVolume(object):
"""Class with volume 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_volume_parameters())
mut_ex_args = [['vol_name', 'vol_id'],
['storage_pool_name', 'storage_pool_id'],
['protection_domain_name', 'protection_domain_id'],
['snapshot_policy_name', 'snapshot_policy_id'],
['vol_id', 'storage_pool_name'],
['vol_id', 'storage_pool_id'],
['vol_id', 'protection_domain_name'],
['vol_id', 'protection_domain_id']]
required_together_args = [['sdc', 'sdc_state']]
required_one_of_args = [['vol_name', 'vol_id']]
# initialize the Ansible module
self.module = AnsibleModule(
argument_spec=self.module_params,
supports_check_mode=False,
mutually_exclusive=mut_ex_args,
required_together=required_together_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:
pd_details = None
if protection_domain_id:
pd_details = self.powerflex_conn.protection_domain.get(
filter_fields={'id': protection_domain_id})
if protection_domain_name:
pd_details = self.powerflex_conn.protection_domain.get(
filter_fields={'name': protection_domain_name})
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)
return pd_details[0]
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_snapshot_policy(self, snap_pol_id=None, snap_pol_name=None):
"""Get snapshot policy details
:param snap_pol_name: Name of the snapshot policy
:param snap_pol_id: ID of the snapshot policy
:return: snapshot policy details
"""
name_or_id = snap_pol_id if snap_pol_id else snap_pol_name
try:
snap_pol_details = None
if snap_pol_id:
snap_pol_details = self.powerflex_conn.snapshot_policy.get(
filter_fields={'id': snap_pol_id})
if snap_pol_name:
snap_pol_details = self.powerflex_conn.snapshot_policy.get(
filter_fields={'name': snap_pol_name})
if not snap_pol_details:
err_msg = "Unable to find the snapshot policy with {0}. " \
"Please enter a valid snapshot policy" \
" name/id.".format(name_or_id)
self.module.fail_json(msg=err_msg)
return snap_pol_details[0]
except Exception as e:
errormsg = "Failed to get the snapshot policy {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,
protection_domain_id=None):
"""Get storage pool details
:param protection_domain_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:
sp_details = None
if storage_pool_id:
sp_details = self.powerflex_conn.storage_pool.get(
filter_fields={'id': storage_pool_id})
if storage_pool_name:
sp_details = self.powerflex_conn.storage_pool.get(
filter_fields={'name': storage_pool_name})
if len(sp_details) > 1 and protection_domain_id is None:
err_msg = "More than one storage pool found with {0}," \
" Please provide protection domain Name/Id" \
" to fetch the unique" \
" pool".format(storage_pool_name)
self.module.fail_json(msg=err_msg)
if len(sp_details) > 1 and protection_domain_id:
sp_details = self.powerflex_conn.storage_pool.get(
filter_fields={'name': storage_pool_name,
'protectionDomainId':
protection_domain_id})
if not sp_details:
err_msg = "Unable to find the storage pool with {0}. " \
"Please enter a valid pool " \
"name/id.".format(name_or_id)
self.module.fail_json(msg=err_msg)
return sp_details[0]
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 get_volume(self, vol_name=None, vol_id=None):
"""Get volume details
:param vol_name: Name of the volume
:param vol_id: ID of the volume
:return: Details of volume if exist.
"""
id_or_name = vol_id if vol_id else vol_name
try:
if vol_name:
volume_details = self.powerflex_conn.volume.get(
filter_fields={'name': vol_name})
else:
volume_details = self.powerflex_conn.volume.get(
filter_fields={'id': vol_id})
if len(volume_details) == 0:
msg = "Volume with identifier {0} not found".format(
id_or_name)
LOG.info(msg)
return None
# Append size in GB in the volume details
if 'sizeInKb' in volume_details[0] and \
volume_details[0]['sizeInKb']:
volume_details[0]['sizeInGB'] = utils.get_size_in_gb(
volume_details[0]['sizeInKb'], 'KB')
# Append storage pool name and id.
sp = None
pd_id = None
if 'storagePoolId' in volume_details[0] and \
volume_details[0]['storagePoolId']:
sp = \
self.get_storage_pool(volume_details[0]['storagePoolId'])
if len(sp) > 0:
volume_details[0]['storagePoolName'] = sp['name']
pd_id = sp['protectionDomainId']
# Append protection domain name and id
if sp and 'protectionDomainId' in sp and \
sp['protectionDomainId']:
pd = self.get_protection_domain(protection_domain_id=pd_id)
volume_details[0]['protectionDomainId'] = pd_id
volume_details[0]['protectionDomainName'] = pd['name']
# Append snapshot policy name and id
if volume_details[0]['snplIdOfSourceVolume'] is not None:
snap_policy_id = volume_details[0]['snplIdOfSourceVolume']
volume_details[0]['snapshotPolicyId'] = snap_policy_id
volume_details[0]['snapshotPolicyName'] = \
self.get_snapshot_policy(snap_policy_id)['name']
return volume_details[0]
except Exception as e:
error_msg = "Failed to get the volume {0} with error {1}"
error_msg = error_msg.format(id_or_name, str(e))
LOG.error(error_msg)
self.module.fail_json(msg=error_msg)
def get_sdc_id(self, sdc_name=None, sdc_ip=None, sdc_id=None):
"""Get the SDC ID
: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 ID of the SDC
"""
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 {0}".format(
id_ip_name)
self.module.fail_json(msg=error_msg)
return sdc_details[0]['id']
except Exception as e:
errormsg = "Failed to get the SDC {0} with error " \
"{1}".format(id_ip_name, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def create_volume(self, vol_name, pool_id, size, vol_type=None,
use_rmcache=None, comp_type=None):
"""Create volume
:param use_rmcache: Boolean indicating whether to use RM cache.
:param comp_type: Type of compression method for the volume.
:param vol_type: Type of volume.
:param size: Size of the volume.
:param pool_id: Id of the storage pool.
:param vol_name: The name of the volume.
:return: Boolean indicating if create operation is successful
"""
try:
if vol_name is None or len(vol_name.strip()) == 0:
self.module.fail_json(msg="Please provide valid volume name.")
if not size:
self.module.fail_json(msg="Size is a mandatory parameter "
"for creating a volume. Please "
"enter a valid size")
pool_data_layout = None
if pool_id:
pool_details = self.get_storage_pool(storage_pool_id=pool_id)
pool_data_layout = pool_details['dataLayout']
if comp_type and pool_data_layout and \
pool_data_layout != "FineGranularity":
err_msg = "compression_type for volume can only be " \
"mentioned when storage pools have Fine " \
"Granularity layout. Storage Pool found" \
" with {0}".format(pool_data_layout)
self.module.fail_json(msg=err_msg)
# Basic volume created.
self.powerflex_conn.volume.create(
storage_pool_id=pool_id, size_in_gb=size, name=vol_name,
volume_type=vol_type, use_rmcache=use_rmcache,
compression_method=comp_type)
return True
except Exception as e:
errormsg = "Create volume {0} operation failed with " \
"error {1}".format(vol_name, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def modify_access_mode(self, vol_id, access_mode_list):
"""Modify access mode of SDCs mapped to volume
:param vol_id: The volume id
:param access_mode_list: List containing SDC ID's
whose access mode is to modified
:return: Boolean indicating if modifying access
mode is successful
"""
try:
changed = False
for temp in access_mode_list:
if temp['accessMode']:
self.powerflex_conn.volume.set_access_mode_for_sdc(
volume_id=vol_id, sdc_id=temp['sdc_id'],
access_mode=temp['accessMode'])
changed = True
return changed
except Exception as e:
errormsg = "Modify access mode of SDC operation failed " \
"with error {0}".format(str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def modify_limits(self, payload):
"""Modify IOPS and bandwidth limits of SDC's mapped to volume
:param payload: Dict containing SDC ID's whose bandwidth and
IOPS is to modified
:return: Boolean indicating if modifying limits is successful
"""
try:
changed = False
if payload['bandwidth_limit'] is not None or \
payload['iops_limit'] is not None:
self.powerflex_conn.volume.set_mapped_sdc_limits(**payload)
changed = True
return changed
except Exception as e:
errormsg = "Modify bandwidth/iops limits of SDC %s operation " \
"failed with error %s" % (payload['sdc_id'], str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def delete_volume(self, vol_id, remove_mode):
"""Delete volume
:param vol_id: The volume id
:param remove_mode: Removal mode for the volume
:return: Boolean indicating if delete operation is successful
"""
try:
self.powerflex_conn.volume.delete(vol_id, remove_mode)
return True
except Exception as e:
errormsg = "Delete volume {0} operation failed with " \
"error {1}".format(vol_id, str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def unmap_volume_from_sdc(self, volume, sdc):
"""Unmap SDC's from volume
:param volume: volume details
:param sdc: List of SDCs to be unmapped
:return: Boolean indicating if unmap operation is successful
"""
current_sdcs = volume['mappedSdcInfo']
current_sdc_ids = []
sdc_id_list = []
sdc_id = None
if current_sdcs:
for temp in current_sdcs:
current_sdc_ids.append(temp['sdcId'])
for temp in sdc:
if 'sdc_name' in temp and temp['sdc_name']:
sdc_id = self.get_sdc_id(sdc_name=temp['sdc_name'])
elif 'sdc_ip' in temp and temp['sdc_ip']:
sdc_id = self.get_sdc_id(sdc_ip=temp['sdc_ip'])
else:
sdc_id = self.get_sdc_id(sdc_id=temp['sdc_id'])
if sdc_id in current_sdc_ids:
sdc_id_list.append(sdc_id)
LOG.info("SDC IDs to remove %s", sdc_id_list)
if len(sdc_id_list) == 0:
return False
try:
for sdc_id in sdc_id_list:
self.powerflex_conn.volume.remove_mapped_sdc(
volume['id'], sdc_id)
return True
except Exception as e:
errormsg = "Unmap SDC {0} from volume {1} failed with error " \
"{2}".format(sdc_id, volume['id'], str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def map_volume_to_sdc(self, volume, sdc):
"""Map SDC's to volume
:param volume: volume details
:param sdc: List of SDCs
:return: Boolean indicating if mapping operation is successful
"""
current_sdcs = volume['mappedSdcInfo']
current_sdc_ids = []
sdc_id_list = []
sdc_map_list = []
sdc_modify_list1 = []
sdc_modify_list2 = []
if current_sdcs:
for temp in current_sdcs:
current_sdc_ids.append(temp['sdcId'])
for temp in sdc:
if 'sdc_name' in temp and temp['sdc_name']:
sdc_id = self.get_sdc_id(sdc_name=temp['sdc_name'])
elif 'sdc_ip' in temp and temp['sdc_ip']:
sdc_id = self.get_sdc_id(sdc_ip=temp['sdc_ip'])
else:
sdc_id = self.get_sdc_id(sdc_id=temp['sdc_id'])
if sdc_id not in current_sdc_ids:
sdc_id_list.append(sdc_id)
temp['sdc_id'] = sdc_id
if 'access_mode' in temp:
temp['access_mode'] = \
get_access_mode(temp['access_mode'])
if 'bandwidth_limit' not in temp:
temp['bandwidth_limit'] = None
if 'iops_limit' not in temp:
temp['iops_limit'] = None
sdc_map_list.append(temp)
else:
access_mode_dict, limits_dict = check_for_sdc_modification(
volume, sdc_id, temp)
if access_mode_dict:
sdc_modify_list1.append(access_mode_dict)
if limits_dict:
sdc_modify_list2.append(limits_dict)
LOG.info("SDC to add: %s", sdc_map_list)
if not sdc_map_list:
return False, sdc_modify_list1, sdc_modify_list2
try:
changed = False
for sdc in sdc_map_list:
payload = {
"volume_id": volume['id'],
"sdc_id": sdc['sdc_id'],
"access_mode": sdc['access_mode'],
"allow_multiple_mappings":
self.module.params['allow_multiple_mappings']
}
self.powerflex_conn.volume.add_mapped_sdc(**payload)
if sdc['bandwidth_limit'] or sdc['iops_limit']:
payload = {
"volume_id": volume['id'],
"sdc_id": sdc['sdc_id'],
"bandwidth_limit": sdc['bandwidth_limit'],
"iops_limit": sdc['iops_limit']
}
self.powerflex_conn.volume.set_mapped_sdc_limits(**payload)
changed = True
return changed, sdc_modify_list1, sdc_modify_list2
except Exception as e:
errormsg = "Mapping volume {0} to SDC {1} " \
"failed with error {2}".format(volume['name'],
sdc['sdc_id'], str(e))
LOG.error(errormsg)
self.module.fail_json(msg=errormsg)
def validate_parameters(self, auto_snap_remove_type, snap_pol_id,
snap_pol_name, delete_snaps, state):
"""Validate the input parameters"""
sdc = self.module.params['sdc']
cap_unit = self.module.params['cap_unit']
size = self.module.params['size']
if sdc:
for temp in sdc:
if (all([temp['sdc_id'], temp['sdc_ip']]) or
all([temp['sdc_id'], temp['sdc_name']]) or
all([temp['sdc_ip'], temp['sdc_name']])):
self.module.fail_json(msg="sdc_id, sdc_ip and sdc_name "
"are mutually exclusive")
if (cap_unit is not None) and not size:
self.module.fail_json(msg="cap_unit can be specified along "
"with size only. Please enter a valid"
" value for size")
if auto_snap_remove_type and snap_pol_name is None \
and snap_pol_id is None:
err_msg = "To remove/detach snapshot policy, please provide" \
" empty snapshot policy name/id along with " \
"auto_snap_remove_type parameter"
LOG.error(err_msg)
self.module.fail_json(msg=err_msg)
if state == "present" and delete_snaps is not None:
self.module.fail_json(
msg="delete_snapshots can be specified only when the state"
" is passed as absent.")
def modify_volume(self, vol_id, modify_dict):
"""
Update the volume attributes
:param vol_id: Id of the volume
:param modify_dict: Dictionary containing the attributes of
volume which are to be updated
:return: True, if the operation is successful
"""
try:
msg = "Dictionary containing attributes which are to be" \
" updated is {0}.".format(str(modify_dict))
LOG.info(msg)
if 'auto_snap_remove_type' in modify_dict:
snap_type = modify_dict['auto_snap_remove_type']
msg = "Removing/detaching the snapshot policy from a " \
"volume. auto_snap_remove_type: {0} and snapshot " \
"policy id: " \
"{1}".format(snap_type, modify_dict['snap_pol_id'])
LOG.info(msg)
self.powerflex_conn.snapshot_policy.remove_source_volume(
modify_dict['snap_pol_id'], vol_id, snap_type)
msg = "The snapshot policy has been {0}ed " \
"successfully".format(snap_type)
LOG.info(msg)
if 'auto_snap_remove_type' not in modify_dict\
and 'snap_pol_id' in modify_dict:
self.powerflex_conn.snapshot_policy.add_source_volume(
modify_dict['snap_pol_id'], vol_id)
msg = "Attached the snapshot policy {0} to volume" \
" successfully.".format(modify_dict['snap_pol_id'])
LOG.info(msg)
if 'new_name' in modify_dict:
self.powerflex_conn.volume.rename(vol_id,
modify_dict['new_name'])
msg = "The name of the volume is updated" \
" to {0} sucessfully.".format(modify_dict['new_name'])
LOG.info(msg)
if 'new_size' in modify_dict:
self.powerflex_conn.volume.extend(vol_id,
modify_dict['new_size'])
msg = "The size of the volume is extended to {0} " \
"sucessfully.".format(str(modify_dict['new_size']))
LOG.info(msg)
if 'use_rmcache' in modify_dict:
self.powerflex_conn.volume.set_use_rmcache(
vol_id, modify_dict['use_rmcache'])
msg = "The use RMcache is updated to {0}" \
" sucessfully.".format(modify_dict['use_rmcache'])
LOG.info(msg)
if 'comp_type' in modify_dict:
self.powerflex_conn.volume.set_compression_method(
vol_id, modify_dict['comp_type'])
msg = "The compression method is updated to {0}" \
" successfully.".format(modify_dict['comp_type'])
LOG.info(msg)
return True
except Exception as e:
err_msg = "Failed to update the volume {0}" \
" with error {1}".format(vol_id, str(e))
LOG.error(err_msg)
self.module.fail_json(msg=err_msg)
def to_modify(self, vol_details, new_size, use_rmcache, comp_type,
new_name, snap_pol_id,
auto_snap_remove_type):
"""
:param vol_details: Details of the volume
:param new_size: Size of the volume
:param use_rmcache: Bool value of use rm cache
:param comp_type: Type of compression method
:param new_name: The new name of the volume
:param snap_pol_id: Id of the snapshot policy
:param auto_snap_remove_type: Whether to remove or detach the policy
:return: Dictionary containing the attributes of
volume which are to be updated
"""
modify_dict = {}
if comp_type:
pool_id = vol_details['storagePoolId']
pool_details = self.get_storage_pool(storage_pool_id=pool_id)
pool_data_layout = pool_details['dataLayout']
if pool_data_layout != "FineGranularity":
err_msg = "compression_type for volume can only be " \
"mentioned when storage pools have Fine " \
"Granularity layout. Storage Pool found" \
" with {0}".format(pool_data_layout)
self.module.fail_json(msg=err_msg)
if comp_type != vol_details['compressionMethod']:
modify_dict['comp_type'] = comp_type
if use_rmcache is not None and \
vol_details['useRmcache'] != use_rmcache:
modify_dict['use_rmcache'] = use_rmcache
vol_size_in_gb = utils.get_size_in_gb(vol_details['sizeInKb'], 'KB')
if new_size is not None and \
not ((vol_size_in_gb - 8) < new_size <= vol_size_in_gb):
modify_dict['new_size'] = new_size
if new_name is not None:
if new_name is None or len(new_name.strip()) == 0:
self.module.fail_json(msg="Please provide valid volume "
"name.")
if new_name != vol_details['name']:
modify_dict['new_name'] = new_name
if snap_pol_id is not None and snap_pol_id == "" and \
auto_snap_remove_type and vol_details['snplIdOfSourceVolume']:
modify_dict['auto_snap_remove_type'] = auto_snap_remove_type
modify_dict['snap_pol_id'] = \
vol_details['snplIdOfSourceVolume']
if snap_pol_id is not None and snap_pol_id != "":
if auto_snap_remove_type and vol_details['snplIdOfSourceVolume']:
err_msg = "To remove/detach a snapshot policy, provide the" \
" snapshot policy name/id as empty string"
self.module.fail_json(msg=err_msg)
if auto_snap_remove_type is None and \
vol_details['snplIdOfSourceVolume'] is None:
modify_dict['snap_pol_id'] = snap_pol_id
return modify_dict
def verify_params(self, vol_details, snap_pol_name, snap_pol_id, pd_name,
pd_id, pool_name, pool_id):
"""
:param vol_details: Details of the volume
:param snap_pol_name: Name of the snapshot policy
:param snap_pol_id: Id of the snapshot policy
:param pd_name: Name of the protection domain
:param pd_id: Id of the protection domain
:param pool_name: Name of the storage pool
:param pool_id: Id of the storage pool
"""
if snap_pol_id and 'snapshotPolicyId' in vol_details and \
snap_pol_id != vol_details['snapshotPolicyId']:
self.module.fail_json(msg="Entered snapshot policy id does not"
" match with the snapshot policy's id"
" attached to the volume. Please enter"
" a correct snapshot policy id.")
if snap_pol_name and 'snapshotPolicyId' in vol_details and \
snap_pol_name != vol_details['snapshotPolicyName']:
self.module.fail_json(msg="Entered snapshot policy name does not"
" match with the snapshot policy's "
"name attached to the volume. Please"
" enter a correct snapshot policy"
" name.")
if pd_id and pd_id != vol_details['protectionDomainId']:
self.module.fail_json(msg="Entered protection domain id does not"
" match with the volume's protection"
" domain id. Please enter a correct"
" protection domain id.")
if pool_id and pool_id != vol_details['storagePoolId']:
self.module.fail_json(msg="Entered storage pool id does"
" not match with the volume's "
"storage pool id. Please enter"
" a correct storage pool id.")
if pd_name and pd_name != vol_details['protectionDomainName']:
self.module.fail_json(msg="Entered protection domain name does"
" not match with the volume's "
"protection domain name. Please enter"
" a correct protection domain name.")
if pool_name and pool_name != vol_details['storagePoolName']:
self.module.fail_json(msg="Entered storage pool name does"
" not match with the volume's "
"storage pool name. Please enter"
" a correct storage pool name.")
def perform_module_operation(self):
"""
Perform different actions on volume based on parameters passed in
the playbook
"""
vol_name = self.module.params['vol_name']
vol_id = self.module.params['vol_id']
vol_type = self.module.params['vol_type']
compression_type = self.module.params['compression_type']
sp_name = self.module.params['storage_pool_name']
sp_id = self.module.params['storage_pool_id']
pd_name = self.module.params['protection_domain_name']
pd_id = self.module.params['protection_domain_id']
snap_pol_name = self.module.params['snapshot_policy_name']
snap_pol_id = self.module.params['snapshot_policy_id']
auto_snap_remove_type = self.module.params['auto_snap_remove_type']
use_rmcache = self.module.params['use_rmcache']
size = self.module.params['size']
cap_unit = self.module.params['cap_unit']
vol_new_name = self.module.params['vol_new_name']
sdc = copy.deepcopy(self.module.params['sdc'])
sdc_state = self.module.params['sdc_state']
delete_snapshots = self.module.params['delete_snapshots']
state = self.module.params['state']
if compression_type:
compression_type = compression_type.capitalize()
if vol_type:
vol_type = get_vol_type(vol_type)
if auto_snap_remove_type:
auto_snap_remove_type = auto_snap_remove_type.capitalize()
# result is a dictionary to contain end state and volume details
changed = False
result = dict(
changed=False,
volume_details={}
)
self.validate_parameters(auto_snap_remove_type, snap_pol_id,
snap_pol_name, delete_snapshots, state)
if not auto_snap_remove_type and\
(snap_pol_name == "" or snap_pol_id == ""):
auto_snap_remove_type = "Detach"
if size:
if not cap_unit:
cap_unit = 'GB'
if cap_unit == 'TB':
size = size * 1024
if pd_name:
pd_details = self.get_protection_domain(pd_name)
if pd_details:
pd_id = pd_details['id']
msg = "Fetched the protection domain details with id {0}," \
" name {1}".format(pd_id, pd_name)
LOG.info(msg)
if sp_name:
sp_details = self.get_storage_pool(storage_pool_name=sp_name,
protection_domain_id=pd_id)
if sp_details:
sp_id = sp_details['id']
msg = "Fetched the storage pool details id {0}," \
" name {1}".format(sp_id, sp_name)
LOG.info(msg)
if snap_pol_name is not None:
snap_pol_details = None
if snap_pol_name:
snap_pol_details = \
self.get_snapshot_policy(snap_pol_name=snap_pol_name)
if snap_pol_details:
snap_pol_id = snap_pol_details['id']
if snap_pol_name == "":
snap_pol_id = ""
msg = "Fetched the snapshot policy details with id {0}," \
" name {1}".format(snap_pol_id, snap_pol_name)
LOG.info(msg)
# get volume details
volume_details = self.get_volume(vol_name, vol_id)
if volume_details:
vol_id = volume_details['id']
msg = "Fetched the volume details {0}".format(str(volume_details))
LOG.info(msg)
if vol_name and volume_details:
self.verify_params(
volume_details, snap_pol_name, snap_pol_id, pd_name, pd_id,
sp_name, sp_id)
# create operation
create_changed = False
if state == 'present' and not volume_details:
if vol_id:
self.module.fail_json(msg="Creation of volume is allowed "
"using vol_name only, "
"vol_id given.")
if vol_new_name:
self.module.fail_json(
msg="vol_new_name parameter is not supported during "
"creation of a volume. Try renaming the volume after"
" the creation.")
create_changed = self.create_volume(vol_name, sp_id, size,
vol_type, use_rmcache,
compression_type)
if create_changed:
volume_details = self.get_volume(vol_name)
vol_id = volume_details['id']
msg = "Volume created successfully, fetched " \
"volume details {0}".format(str(volume_details))
LOG.info(msg)
# checking if basic volume parameters are modified or not.
modify_dict = {}
if volume_details and state == 'present':
modify_dict = self.to_modify(
volume_details, size, use_rmcache, compression_type,
vol_new_name, snap_pol_id, auto_snap_remove_type)
msg = "Parameters to be modified are as" \
" follows: {0}".format(str(modify_dict))
LOG.info(msg)
# Mapping the SDCs to a volume
mode_changed = False
limits_changed = False
map_changed = False
if state == 'present' and volume_details and sdc and \
sdc_state == 'mapped':
map_changed, access_mode_list, limits_list = \
self.map_volume_to_sdc(volume_details, sdc)
if len(access_mode_list) > 0:
mode_changed = self.modify_access_mode(vol_id,
access_mode_list)
if len(limits_list) > 0:
for temp in limits_list:
payload = {
"volume_id": volume_details['id'],
"sdc_id": temp['sdc_id'],
"bandwidth_limit": temp['bandwidth_limit'],
"iops_limit": temp['iops_limit']
}
limits_changed = self.modify_limits(payload)
# Unmap the SDCs to a volume
unmap_changed = False
if state == 'present' and volume_details and sdc and \
sdc_state == 'unmapped':
unmap_changed = self.unmap_volume_from_sdc(volume_details, sdc)
# Update the basic volume attributes
modify_changed = False
if modify_dict and state == 'present':
modify_changed = self.modify_volume(vol_id, modify_dict)
# delete operation
del_changed = False
if state == 'absent' and volume_details:
if delete_snapshots is True:
delete_snapshots = 'INCLUDING_DESCENDANTS'
if delete_snapshots is None or delete_snapshots is False:
delete_snapshots = 'ONLY_ME'
del_changed = \
self.delete_volume(vol_id, delete_snapshots)
if modify_changed or unmap_changed or map_changed or create_changed\
or del_changed or mode_changed or limits_changed:
changed = True
# Returning the updated volume details
if state == 'present':
vol_details = self.show_output(vol_id)
result['volume_details'] = vol_details
result['changed'] = changed
self.module.exit_json(**result)
def show_output(self, vol_id):
"""Show volume details
:param vol_id: ID of the volume
:return: Details of volume if exist.
"""
try:
volume_details = self.powerflex_conn.volume.get(
filter_fields={'id': vol_id})
if len(volume_details) == 0:
msg = "Volume with identifier {0} not found".format(
vol_id)
LOG.error(msg)
return None
# Append size in GB in the volume details
if 'sizeInKb' in volume_details[0] and \
volume_details[0]['sizeInKb']:
volume_details[0]['sizeInGB'] = utils.get_size_in_gb(
volume_details[0]['sizeInKb'], 'KB')
# Append storage pool name and id.
sp = None
pd_id = None
if 'storagePoolId' in volume_details[0] and \
volume_details[0]['storagePoolId']:
sp = \
self.get_storage_pool(volume_details[0]['storagePoolId'])
if len(sp) > 0:
volume_details[0]['storagePoolName'] = sp['name']
pd_id = sp['protectionDomainId']
# Append protection domain name and id
if sp and 'protectionDomainId' in sp and \
sp['protectionDomainId']:
pd = self.get_protection_domain(protection_domain_id=pd_id)
volume_details[0]['protectionDomainId'] = pd_id
volume_details[0]['protectionDomainName'] = pd['name']
# Append snapshot policy name and id
if volume_details[0]['snplIdOfSourceVolume'] is not None:
snap_policy_id = volume_details[0]['snplIdOfSourceVolume']
volume_details[0]['snapshotPolicyId'] = snap_policy_id
volume_details[0]['snapshotPolicyName'] = \
self.get_snapshot_policy(snap_policy_id)['name']
else:
volume_details[0]['snapshotPolicyId'] = None
volume_details[0]['snapshotPolicyName'] = None
# Append the list of snapshots associated with the volume
list_of_snaps = self.powerflex_conn.volume.get(
filter_fields={'ancestorVolumeId': volume_details[0]['id']})
volume_details[0]['snapshotsList'] = list_of_snaps
# Append statistics
statistics = self.powerflex_conn.volume.get_statistics(volume_details[0]['id'])
volume_details[0]['statistics'] = statistics if statistics else {}
return volume_details[0]
except Exception as e:
error_msg = "Failed to get the volume {0} with error {1}"
error_msg = error_msg.format(vol_id, str(e))
LOG.error(error_msg)
self.module.fail_json(msg=error_msg)
def check_for_sdc_modification(volume, sdc_id, sdc_details):
"""
:param volume: The volume details
:param sdc_id: The ID of the SDC
:param sdc_details: The details of SDC
:return: Dictionary with SDC attributes to be modified
"""
access_mode_dict = dict()
limits_dict = dict()
for sdc in volume['mappedSdcInfo']:
if sdc['sdcId'] == sdc_id:
if sdc['accessMode'] != \
get_access_mode(sdc_details['access_mode']):
access_mode_dict['sdc_id'] = sdc_id
access_mode_dict['accessMode'] = get_access_mode(
sdc_details['access_mode'])
if sdc['limitIops'] != sdc_details['iops_limit'] or \
sdc['limitBwInMbps'] != sdc_details['bandwidth_limit']:
limits_dict['sdc_id'] = sdc_id
limits_dict['iops_limit'] = None
limits_dict['bandwidth_limit'] = None
if sdc['limitIops'] != sdc_details['iops_limit']:
limits_dict['iops_limit'] = sdc_details['iops_limit']
if sdc['limitBwInMbps'] != \
get_limits_in_mb(sdc_details['bandwidth_limit']):
limits_dict['bandwidth_limit'] = \
sdc_details['bandwidth_limit']
break
return access_mode_dict, limits_dict
def get_limits_in_mb(limits):
"""
:param limits: Limits in KB
:return: Limits in MB
"""
if limits:
return limits / 1024
def get_access_mode(access_mode):
"""
:param access_mode: Access mode of the SDC
:return: The enum for the access mode
"""
access_mode_dict = {
"READ_WRITE": "ReadWrite",
"READ_ONLY": "ReadOnly",
"NO_ACCESS": "NoAccess"
}
return access_mode_dict.get(access_mode)
def get_vol_type(vol_type):
"""
:param vol_type: Type of the volume
:return: Corresponding value for the entered vol_type
"""
vol_type_dict = {
"THICK_PROVISIONED": "ThickProvisioned",
"THIN_PROVISIONED": "ThinProvisioned",
}
return vol_type_dict.get(vol_type)
def get_powerflex_volume_parameters():
"""This method provide parameter required for the volume
module on PowerFlex"""
return dict(
vol_name=dict(), vol_id=dict(),
storage_pool_name=dict(), storage_pool_id=dict(),
protection_domain_name=dict(), protection_domain_id=dict(),
use_rmcache=dict(type='bool'), snapshot_policy_name=dict(),
snapshot_policy_id=dict(),
size=dict(type='int'),
cap_unit=dict(choices=['GB', 'TB']),
vol_type=dict(choices=['THICK_PROVISIONED', 'THIN_PROVISIONED']),
compression_type=dict(choices=['NORMAL', 'NONE']),
auto_snap_remove_type=dict(choices=['detach', 'remove']),
vol_new_name=dict(),
allow_multiple_mappings=dict(type='bool'),
delete_snapshots=dict(type='bool'),
sdc=dict(
type='list', elements='dict', options=dict(
sdc_id=dict(), sdc_ip=dict(),
sdc_name=dict(),
access_mode=dict(choices=['READ_WRITE', 'READ_ONLY',
'NO_ACCESS']),
bandwidth_limit=dict(type='int'),
iops_limit=dict(type='int')
)
),
sdc_state=dict(choices=['mapped', 'unmapped']),
state=dict(required=True, type='str', choices=['present', 'absent'])
)
def main():
""" Create PowerFlex volume object and perform actions on it
based on user input from playbook"""
obj = PowerFlexVolume()
obj.perform_module_operation()
if __name__ == '__main__':
main()