diff options
Diffstat (limited to 'ansible_collections/dellemc/powerflex/plugins/modules/volume.py')
-rw-r--r-- | ansible_collections/dellemc/powerflex/plugins/modules/volume.py | 1599 |
1 files changed, 1599 insertions, 0 deletions
diff --git a/ansible_collections/dellemc/powerflex/plugins/modules/volume.py b/ansible_collections/dellemc/powerflex/plugins/modules/volume.py new file mode 100644 index 000000000..9c1e1cd29 --- /dev/null +++ b/ansible_collections/dellemc/powerflex/plugins/modules/volume.py @@ -0,0 +1,1599 @@ +#!/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 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) <ansible.team@dell.com> +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() |