Repository URL to install this package:
|
Version:
6.0.0 ▾
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = r"""
---
module: purefa_volume
version_added: '1.0.0'
short_description: Manage volumes on Pure Storage FlashArrays
description:
- Create, delete or extend the capacity of a volume on Pure Storage FlashArray.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
name:
description:
- The name of the volume.
- Volume could be created in a POD with this syntax POD_NAME::VOLUME_NAME.
- Volume could be created in a volume group with this syntax VG_NAME/VOLUME_NAME.
- Multi-volume support available from Purity//FA 6.0.0
B(***NOTE***) Manual deletion or eradication of individual volumes created
using multi-volume will cause idempotency to fail
- Multi-volume support only exists for volume creation
type: str
required: true
target:
description:
- The name of the target volume, if copying.
type: str
state:
description:
- Define whether the volume should exist or not.
default: present
choices: [ absent, present ]
type: str
eradicate:
description:
- Define whether to eradicate the volume on delete or leave in trash.
type: bool
default: 'no'
overwrite:
description:
- Define whether to overwrite a target volume if it already exisits.
type: bool
default: 'no'
size:
description:
- Volume size in M, G, T or P units.
type: str
count:
description:
- Number of volumes to be created in a multiple volume creation
- Only supported from Purity//FA v6.0.0 and higher
type: int
start:
description:
- Number at which to start the multiple volume creation index
- Only supported from Purity//FA v6.0.0 and higher
type: int
default: 0
digits:
description:
- Number of digits to use for multiple volume count. This
will pad the index number with zeros where necessary
- Only supported from Purity//FA v6.0.0 and higher
- Range is between 1 and 10
type: int
default: 1
suffix:
description:
- Suffix string, if required, for multiple volume create
- Volume names will be formed as I(<name>#I<suffix>), where
I(#) is a placeholder for the volume index
See associated descriptions
- Only supported from Purity//FA v6.0.0 and higher
type: str
bw_qos:
description:
- Bandwidth limit for volume in M or G units.
M will set MB/s
G will set GB/s
To clear an existing QoS setting use 0 (zero)
type: str
aliases: [ qos ]
iops_qos:
description:
- IOPs limit for volume - use value or K or M
K will mean 1000
M will mean 1000000
To clear an existing IOPs setting use 0 (zero)
type: str
move:
description:
- Move a volume in and out of a pod or vgroup
- Provide the name of pod or vgroup to move the volume to
- Pod and Vgroup names must be unique in the array
- To move to the local array, specify C(local)
- This is not idempotent - use C(ignore_errors) in the play
type: str
rename:
description:
- Value to rename the specified volume to.
- Rename only applies to the container the current volumes is in.
- There is no requirement to specify the pod or vgroup name as this is implied.
type: str
pgroup:
description:
- Name of exisitng, not deleted, protection group to add volume to
- Only application for volume(s) creation
type: str
version_added: 1.8.0
priority_operator:
description:
- DMM Priority Adjustment operator
type: str
choices: [ '=', '+', '-' ]
version_added: '1.13.0'
priority_value:
description:
- DMM Priority Adjustment value
type: int
choices: [ -10, 0, 10 ]
version_added: '1.13.0'
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
EXAMPLES = r"""
- name: Create new volume named foo with a QoS limit
purestorage.flasharray.purefa_volume:
name: foo
size: 1T
bw_qos: 58M
iops_qos: 23K
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Create new volume named foo with a DMM priority (Purity//FA 6.1.2+)
purestorage.flasharray.purefa_volume:
name: foo
size: 1T
priority_operator: +
priorty_value: 10
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Create new volume named foo in pod bar in protection group pg1
purestorage.flasharray.purefa_volume:
name: bar::foo
prgoup: pg1
size: 1T
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Create 10 volumes with index starting at 10 but padded with 3 digits
purestorage.flasharray.purefa_volume:
name: foo
size: 1T
suffix: bar
count: 10
start: 10
digits: 3
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Extend the size of an existing volume named foo
purestorage.flasharray.purefa_volume:
name: foo
size: 2T
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Delete and eradicate volume named foo
purestorage.flasharray.purefa_volume:
name: foo
eradicate: yes
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
- name: Create clone of volume bar named foo
purestorage.flasharray.purefa_volume:
name: foo
target: bar
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Overwrite volume bar with volume foo
purestorage.flasharray.purefa_volume:
name: foo
target: bar
overwrite: yes
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Clear volume QoS from volume foo
purestorage.flasharray.purefa_volume:
name: foo
bw_qos: 0
iops_qos: 0
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Move local volume foo from local array to pod bar
purestorage.flasharray.purefa_volume:
name: foo
move: bar
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Move volume foo in pod bar to local array
purestorage.flasharray.purefa_volume:
name: bar::foo
move: local
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Move volume foo in pod bar to vgroup fin
purestorage.flasharray.purefa_volume:
name: bar::foo
move: fin
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
"""
RETURN = r"""
volume:
description: A dictionary describing the changed volume. Only some
attributes below will be returned with various actions.
type: dict
returned: success
contains:
source:
description: Volume name of source volume used for volume copy
type: str
serial:
description: Volume serial number
type: str
sample: '361019ECACE43D83000120A4'
nvme_nguid:
description: Volume NVMe namespace globally unique identifier
type: str
sample: 'eui.00cd6b99ef25864724a937c5000be684'
page83_naa:
description: Volume NAA canonical name
type: str
sample: 'naa.624a9370361019ecace43db3000120a4'
created:
description: Volume creation time
type: str
sample: '2019-03-13T22:49:24Z'
name:
description: Volume name
type: str
size:
description: Volume size in bytes
type: int
bandwidth_limit:
description: Volume bandwidth limit in bytes/sec
type: int
iops_limit:
description: Volume IOPs limit
type: int
priority_operator:
description: DMM Priority Adjustment operator
type: str
priority_value:
description: DMM Priority Adjustment value
type: int
"""
HAS_PURESTORAGE = True
try:
from pypureclient import flasharray
except ImportError:
HAS_PURESTORAGE = False
import re
import time
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
get_array,
get_system,
purefa_argument_spec,
)
QOS_API_VERSION = "1.14"
VGROUPS_API_VERSION = "1.13"
POD_API_VERSION = "1.13"
AC_QOS_VERSION = "1.16"
OFFLOAD_API_VERSION = "1.16"
IOPS_API_VERSION = "1.17"
MULTI_VOLUME_VERSION = "2.2"
PROMOTE_API_VERSION = "1.19"
PURE_OUI = "naa.624a9370"
PRIORITY_API_VERSION = "2.11"
def _create_nguid(serial):
nguid = "eui.00" + serial[0:14] + "24a937" + serial[-10:]
return nguid
def get_pod(module, array):
"""Get ActiveCluster Pod"""
pod_name = module.params["pgroup"].split("::")[0]
try:
return array.get_pod(pod=pod_name)
except Exception:
return None
def get_pending_pgroup(module, array):
"""Get Protection Group"""
pgroup = None
if ":" in module.params["pgroup"]:
if "::" not in module.params["pgroup"]:
for pgrp in array.list_pgroups(pending=True, on="*"):
if pgrp["name"] == module.params["pgroup"] and pgrp["time_remaining"]:
pgroup = pgrp
break
else:
for pgrp in array.list_pgroups(pending=True):
if pgrp["name"] == module.params["pgroup"] and pgrp["time_remaining"]:
pgroup = pgrp
break
else:
for pgrp in array.list_pgroups(pending=True):
if pgrp["name"] == module.params["pgroup"] and pgrp["time_remaining"]:
pgroup = pgrp
break
return pgroup
def get_pgroup(module, array):
"""Get Protection Group"""
pgroup = None
if ":" in module.params["pgroup"]:
if "::" not in module.params["pgroup"]:
for pgrp in array.list_pgroups(on="*"):
if pgrp["name"] == module.params["pgroup"]:
pgroup = pgrp
break
else:
for pgrp in array.list_pgroups():
if pgrp["name"] == module.params["pgroup"]:
pgroup = pgrp
break
else:
for pgrp in array.list_pgroups():
if pgrp["name"] == module.params["pgroup"]:
pgroup = pgrp
break
return pgroup
def human_to_bytes(size):
"""Given a human-readable byte string (e.g. 2G, 30M),
return the number of bytes. Will return 0 if the argument has
unexpected form.
"""
bytes = size[:-1]
unit = size[-1].upper()
if bytes.isdigit():
bytes = int(bytes)
if unit == "P":
bytes *= 1125899906842624
elif unit == "T":
bytes *= 1099511627776
elif unit == "G":
bytes *= 1073741824
elif unit == "M":
bytes *= 1048576
elif unit == "K":
bytes *= 1024
else:
bytes = 0
else:
bytes = 0
return bytes
def human_to_real(iops):
"""Given a human-readable IOPs string (e.g. 2K, 30M),
return the real number. Will return 0 if the argument has
unexpected form.
"""
digit = iops[:-1]
unit = iops[-1].upper()
if unit.isdigit():
digit = iops
elif digit.isdigit():
digit = int(digit)
if unit == "M":
digit *= 1000000
elif unit == "K":
digit *= 1000
else:
digit = 0
else:
digit = 0
return digit
def get_multi_volumes(module, destroyed=False):
"""Return True is all volumes exist or None"""
names = []
array = get_array(module)
for vol_num in range(
module.params["start"], module.params["count"] + module.params["start"]
):
names.append(
module.params["name"]
+ str(vol_num).zfill(module.params["digits"])
+ module.params["suffix"]
)
return bool(array.get_volumes(names=names, destroyed=destroyed).status_code == 200)
def get_volume(module, array):
"""Return Volume or None"""
try:
return array.get_volume(module.params["name"])
except Exception:
return None
def get_endpoint(name, array):
"""Return Endpoint or None"""
try:
return array.get_volume(name, pending=True, protocol_endpoint=True)
except Exception:
return None
def get_destroyed_volume(vol, array):
"""Return Destroyed Volume or None"""
try:
return bool(array.get_volume(vol, pending=True)["time_remaining"] != "")
except Exception:
return False
def get_destroyed_endpoint(vol, array):
"""Return Destroyed Endpoint or None"""
try:
return bool(
array.get_volume(vol, protocol_endpoint=True, pending=True)[
"time_remaining"
]
!= ""
)
except Exception:
return False
def get_target(module, array):
"""Return Volume or None"""
try:
return array.get_volume(module.params["target"])
except Exception:
return None
def check_vgroup(module, array):
"""Check is the requested VG to create volume in exists"""
vg_exists = False
api_version = array._list_available_rest_versions()
if VGROUPS_API_VERSION in api_version:
vg_name = module.params["name"].split("/")[0]
try:
vgs = array.list_vgroups()
except Exception:
module.fail_json(msg="Failed to get volume groups list. Check array.")
for vgroup in range(0, len(vgs)):
if vg_name == vgs[vgroup]["name"]:
vg_exists = True
break
else:
module.fail_json(
msg="VG volumes are not supported. Please upgrade your FlashArray."
)
return vg_exists
def check_pod(module, array):
"""Check is the requested pod to create volume in exists"""
pod_exists = False
api_version = array._list_available_rest_versions()
if POD_API_VERSION in api_version:
pod_name = module.params["name"].split("::")[0]
try:
pods = array.list_pods()
except Exception:
module.fail_json(msg="Failed to get pod list. Check array.")
for pod in range(0, len(pods)):
if pod_name == pods[pod]["name"]:
pod_exists = True
break
else:
module.fail_json(
msg="Pod volumes are not supported. Please upgrade your FlashArray."
)
return pod_exists
def create_volume(module, array):
"""Create Volume"""
volfact = []
changed = False
api_version = array._list_available_rest_versions()
if "/" in module.params["name"] and not check_vgroup(module, array):
module.fail_json(
msg="Failed to create volume {0}. Volume Group does not exist.".format(
module.params["name"]
)
)
if "::" in module.params["name"]:
if not check_pod(module, array):
module.fail_json(
msg="Failed to create volume {0}. Pod does not exist".format(
module.params["name"]
)
)
pod_name = module.params["name"].split("::")[0]
if PROMOTE_API_VERSION in api_version:
if array.get_pod(pod_name)["promotion_status"] == "demoted":
module.fail_json(msg="Volume cannot be created in a demoted pod")
if module.params["bw_qos"] or module.params["iops_qos"]:
if AC_QOS_VERSION not in api_version:
module.warn(
"Pods cannot cannot contain volumes with QoS settings. Ignoring..."
)
module.params["bw_qos"] = module.params["iops_qos"] = None
if not module.params["size"]:
module.fail_json(msg="Size for a new volume must be specified")
if module.params["bw_qos"] or module.params["iops_qos"]:
if module.params["bw_qos"] and QOS_API_VERSION in api_version:
if module.params["iops_qos"] and IOPS_API_VERSION in api_version:
if module.params["bw_qos"] and not module.params["iops_qos"]:
if int(human_to_bytes(module.params["bw_qos"])) in range(
1048576, 549755813888
):
changed = True
if not module.check_mode:
try:
volfact = array.create_volume(
module.params["name"],
module.params["size"],
bandwidth_limit=module.params["bw_qos"],
)
volfact["page83_naa"] = (
PURE_OUI + volfact["serial"].lower()
)
volfact["nvme_nguid"] = _create_nguid(
volfact["serial"].lower()
)
except Exception:
module.fail_json(
msg="Volume {0} creation failed.".format(
module.params["name"]
)
)
else:
module.fail_json(
msg="Bandwidth QoS value {0} out of range.".format(
module.params["bw_qos"]
)
)
elif module.params["iops_qos"] and not module.params["bw_qos"]:
if (
100000000
>= int(human_to_real(module.params["iops_qos"]))
>= 100
):
changed = True
if not module.check_mode:
try:
volfact = array.create_volume(
module.params["name"],
module.params["size"],
iops_limit=module.params["iops_qos"],
)
volfact["page83_naa"] = (
PURE_OUI + volfact["serial"].lower()
)
volfact["nvme_nguid"] = _create_nguid(
volfact["serial"].lower()
)
except Exception:
module.fail_json(
msg="Volume {0} creation failed.".format(
module.params["name"]
)
)
else:
module.fail_json(
msg="IOPs QoS value {0} out of range.".format(
module.params["iops_qos"]
)
)
else:
bw_qos_size = int(human_to_bytes(module.params["bw_qos"]))
if int(human_to_real(module.params["iops_qos"])) in range(
100, 100000000
) and bw_qos_size in range(1048576, 549755813888):
changed = True
if not module.check_mode:
try:
volfact = array.create_volume(
module.params["name"],
module.params["size"],
iops_limit=module.params["iops_qos"],
bandwidth_limit=module.params["bw_qos"],
)
volfact["page83_naa"] = (
PURE_OUI + volfact["serial"].lower()
)
volfact["nvme_nguid"] = _create_nguid(
volfact["serial"].lower()
)
except Exception:
module.fail_json(
msg="Volume {0} creation failed.".format(
module.params["name"]
)
)
else:
module.fail_json(
msg="IOPs or Bandwidth QoS value out of range."
)
else:
if module.params["bw_qos"]:
if int(human_to_bytes(module.params["bw_qos"])) in range(
1048576, 549755813888
):
changed = True
if not module.check_mode:
try:
volfact = array.create_volume(
module.params["name"],
module.params["size"],
bandwidth_limit=module.params["bw_qos"],
)
volfact["page83_naa"] = (
PURE_OUI + volfact["serial"].lower()
)
volfact["nvme_nguid"] = _create_nguid(
volfact["serial"].lower()
)
except Exception:
module.fail_json(
msg="Volume {0} creation failed.".format(
module.params["name"]
)
)
else:
module.fail_json(
msg="Bandwidth QoS value {0} out of range.".format(
module.params["bw_qos"]
)
)
else:
changed = True
if not module.check_mode:
try:
volfact = array.create_volume(
module.params["name"], module.params["size"]
)
volfact["page83_naa"] = PURE_OUI + volfact["serial"].lower()
volfact["nvme_nguid"] = _create_nguid(
volfact["serial"].lower()
)
except Exception:
module.fail_json(
msg="Volume {0} creation failed.".format(
module.params["name"]
)
)
else:
changed = True
if not module.check_mode:
try:
volfact = array.create_volume(
module.params["name"], module.params["size"]
)
volfact["page83_naa"] = PURE_OUI + volfact["serial"].lower()
volfact["nvme_nguid"] = _create_nguid(volfact["serial"].lower())
except Exception:
module.fail_json(
msg="Volume {0} creation failed.".format(module.params["name"])
)
if PRIORITY_API_VERSION in api_version and module.params["priority_operator"]:
arrayv6 = get_array(module)
volume = flasharray.VolumePatch(
priority_adjustment=flasharray.PriorityAdjustment(
priority_adjustment_operator=module.params["priority_operator"],
priority_adjustment_value=module.params["priority_value"],
)
)
res = arrayv6.patch_volumes(names=[module.params["name"]], volume=volume)
if res.status_code != 200:
arrayv6.patch_volumes(
names=[module.params["name"]],
volume=flasharray.VolumePatch(destroyed=True),
)
arrayv6.delete_volumes(names=[module.params["name"]])
module.fail_json(
msg="Failed to set DMM Priority Adjustment on volume {0}. Error: {1}".format(
module.params["name"], res.errors[0].message
)
)
else:
volfact["priority_operator"] = module.params["priority_operator"]
volfact["priority_value"] = module.params["priority_value"]
if module.params["pgroup"]:
changed = True
if not module.check_mode:
try:
array.set_pgroup(
module.params["pgroup"], addvollist=[module.params["name"]]
)
except Exception:
module.warn_json(
"Failed to add {0} to protection group {1}.".format(
module.params["name"], module.params["pgroup"]
)
)
module.exit_json(changed=changed, volume=volfact)
def create_multi_volume(module, array):
"""Create Volume"""
volfact = {}
changed = True
api_version = array._list_available_rest_versions()
bw_qos_size = iops_qos_size = 0
names = []
if "/" in module.params["name"] and not check_vgroup(module, array):
module.fail_json(
msg="Multi-volume create failed. Volume Group {0} does not exist.".format(
module.params["name"].split("/")[0]
)
)
if "::" in module.params["name"]:
if not check_pod(module, array):
module.fail_json(
msg="Multi-volume create failed. Pod {0} does not exist".format(
module.params["name"].split(":")[0]
)
)
pod_name = module.params["name"].split("::")[0]
if PROMOTE_API_VERSION in api_version:
if array.get_pod(pod_name)["promotion_status"] == "demoted":
module.fail_json(msg="Volume cannot be created in a demoted pod")
array = get_array(module)
for vol_num in range(
module.params["start"], module.params["count"] + module.params["start"]
):
names.append(
module.params["name"]
+ str(vol_num).zfill(module.params["digits"])
+ module.params["suffix"]
)
if module.params["bw_qos"]:
bw_qos = int(human_to_bytes(module.params["bw_qos"]))
if bw_qos in range(1048576, 549755813888):
bw_qos_size = bw_qos
else:
module.fail_json(msg="Bandwidth QoS value out of range.")
if module.params["iops_qos"]:
iops_qos = int(human_to_real(module.params["iops_qos"]))
if iops_qos in range(100, 100000000):
iops_qos_size = iops_qos
else:
module.fail_json(msg="IOPs QoS value out of range.")
if bw_qos_size != 0 and iops_qos_size != 0:
vols = flasharray.VolumePost(
provisioned=human_to_bytes(module.params["size"]),
qos=flasharray.Qos(bandwidth_limit=bw_qos_size, iops_limit=iops_qos_size),
subtype="regular",
)
elif bw_qos_size == 0 and iops_qos_size == 0:
vols = flasharray.VolumePost(
provisioned=human_to_bytes(module.params["size"]), subtype="regular"
)
elif bw_qos_size == 0 and iops_qos_size != 0:
vols = flasharray.VolumePost(
provisioned=human_to_bytes(module.params["size"]),
qos=flasharray.Qos(iops_limit=iops_qos_size),
subtype="regular",
)
elif bw_qos_size != 0 and iops_qos_size == 0:
vols = flasharray.VolumePost(
provisioned=human_to_bytes(module.params["size"]),
qos=flasharray.Qos(bandwidth_limit=bw_qos_size),
subtype="regular",
)
if not module.check_mode:
res = array.post_volumes(names=names, volume=vols)
if res.status_code != 200:
module.fail_json(
msg="Multi-Volume {0}#{1} creation failed: {2}".format(
module.params["name"],
module.params["suffix"],
res.errors[0].message,
)
)
else:
if (
PRIORITY_API_VERSION in api_version
and module.params["priority_operator"]
):
volume = flasharray.VolumePatch(
priority_adjustment=flasharray.PriorityAdjustment(
priority_adjustment_operator=module.params["priority_operator"],
priority_adjustment_value=module.params["priority_value"],
)
)
prio_res = array.patch_volumes(names=names, volume=volume)
if prio_res.status_code != 200:
array.patch_volumes(
names=names,
volume=flasharray.VolumePatch(destroyed=True),
)
array.delete_volumes(names=names)
module.fail_json(
msg="Failed to set DMM Priority Adjustment on volumes. Error: {0}".format(
prio_res.errors[0].message
)
)
prio_temp = list(prio_res.items)
temp = list(res.items)
for count in range(0, len(temp)):
vol_name = temp[count].name
volfact[vol_name] = {
"size": temp[count].provisioned,
"serial": temp[count].serial,
"created": time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(temp[count].created / 1000)
),
"page83_naa": PURE_OUI + temp[count].serial.lower(),
"nvme_nguid": _create_nguid(temp[count].serial.lower()),
}
if bw_qos_size != 0:
volfact[vol_name]["bandwidth_limit"] = temp[
count
].qos.bandwidth_limit
if iops_qos_size != 0:
volfact[vol_name]["iops_limit"] = temp[count].qos.iops_limit
if (
PRIORITY_API_VERSION in api_version
and module.params["priority_operator"]
):
volfact[vol_name]["priority_operator"] = prio_temp[
count
].priority_adjustment.priority_adjustment_operator
volfact[vol_name]["priority_value"] = prio_temp[
count
].priority_adjustment.priority_adjustment_value
if module.params["pgroup"]:
if not module.check_mode:
res = array.post_protection_groups_volumes(
group_names=[module.params["pgroup"]], member_names=names
)
if res.status_code != 200:
module.warn(
"Failed to add {0} to protection group {1}.".format(
module.params["name"], module.params["pgroup"]
)
)
module.exit_json(changed=changed, volfact=volfact)
def copy_from_volume(module, array):
"""Create Volume Clone"""
volfact = []
changed = False
tgt = get_target(module, array)
if tgt is None:
changed = True
if not module.check_mode:
try:
volfact = array.copy_volume(
module.params["name"], module.params["target"]
)
volfact["page83_naa"] = PURE_OUI + volfact["serial"].lower()
volfact["nvme_nguid"] = _create_nguid(volfact["serial"].lower())
changed = True
except Exception:
module.fail_json(
msg="Copy volume {0} to volume {1} failed.".format(
module.params["name"], module.params["target"]
)
)
elif tgt is not None and module.params["overwrite"]:
changed = True
if not module.check_mode:
try:
volfact = array.copy_volume(
module.params["name"],
module.params["target"],
overwrite=module.params["overwrite"],
)
volfact["page83_naa"] = PURE_OUI + volfact["serial"].lower()
volfact["nvme_nguid"] = _create_nguid(volfact["serial"].lower())
changed = True
except Exception:
module.fail_json(
msg="Copy volume {0} to volume {1} failed.".format(
module.params["name"], module.params["target"]
)
)
module.exit_json(changed=changed, volume=volfact)
def update_volume(module, array):
"""Update Volume size and/or QoS"""
volfact = []
changed = False
api_version = array._list_available_rest_versions()
vol = array.get_volume(module.params["name"])
vol_qos = array.get_volume(module.params["name"], qos=True)
if QOS_API_VERSION in api_version:
if vol_qos["bandwidth_limit"] is None:
vol_qos["bandwidth_limit"] = 0
if IOPS_API_VERSION in api_version:
if vol_qos["iops_limit"] is None:
vol_qos["iops_limit"] = 0
if "::" in module.params["name"]:
if module.params["bw_qos"] or module.params["iops_qos"]:
if AC_QOS_VERSION not in api_version:
module.warn(
"Pods cannot cannot contain volumes with QoS settings. Ignoring..."
)
module.params["bw_qos"] = module.params["iops_qos"] = None
if module.params["size"]:
if human_to_bytes(module.params["size"]) != vol["size"]:
if human_to_bytes(module.params["size"]) > vol["size"]:
changed = True
if not module.check_mode:
try:
volfact = array.extend_volume(
module.params["name"], module.params["size"]
)
except Exception:
module.fail_json(
msg="Volume {0} resize failed.".format(
module.params["name"]
)
)
if module.params["bw_qos"] and QOS_API_VERSION in api_version:
if human_to_bytes(module.params["bw_qos"]) != vol_qos["bandwidth_limit"]:
if module.params["bw_qos"] == "0":
changed = True
if not module.check_mode:
try:
volfact = array.set_volume(
module.params["name"], bandwidth_limit=""
)
except Exception:
module.fail_json(
msg="Volume {0} Bandwidth QoS removal failed.".format(
module.params["name"]
)
)
elif int(human_to_bytes(module.params["bw_qos"])) in range(
1048576, 549755813888
):
changed = True
if not module.check_mode:
try:
volfact = array.set_volume(
module.params["name"],
bandwidth_limit=module.params["bw_qos"],
)
except Exception:
module.fail_json(
msg="Volume {0} Bandwidth QoS change failed.".format(
module.params["name"]
)
)
else:
module.fail_json(
msg="Bandwidth QoS value {0} out of range.".format(
module.params["bw_qos"]
)
)
if module.params["iops_qos"] and IOPS_API_VERSION in api_version:
if human_to_real(module.params["iops_qos"]) != vol_qos["iops_limit"]:
if module.params["iops_qos"] == "0":
changed = True
if not module.check_mode:
try:
volfact = array.set_volume(module.params["name"], iops_limit="")
except Exception:
module.fail_json(
msg="Volume {0} IOPs QoS removal failed.".format(
module.params["name"]
)
)
elif int(human_to_real(module.params["iops_qos"])) in range(100, 100000000):
changed = True
if not module.check_mode:
try:
volfact = array.set_volume(
module.params["name"], iops_limit=module.params["iops_qos"]
)
except Exception:
module.fail_json(
msg="Volume {0} IOPs QoS change failed.".format(
module.params["name"]
)
)
else:
module.fail_json(
msg="Bandwidth QoS value {0} out of range.".format(
module.params["bw_qos"]
)
)
if PRIORITY_API_VERSION in api_version and module.params["priority_operator"]:
arrayv6 = get_array(module)
change_prio = False
volv6 = list(arrayv6.get_volumes(names=[module.params["name"]]).items)[0]
if (
module.params["priority_operator"]
!= volv6.priority_adjustment.priority_adjustment_operator
):
change_prio = True
newop = module.params["priority_operator"]
else:
newop = volv6.priority_adjustment.priority_adjustment_operator
if (
module.params["priority_value"]
and module.params["priority_value"]
!= volv6.priority_adjustment.priority_adjustment_value
):
change_prio = True
newval = module.params["priority_value"]
elif (
not module.params["priority_value"]
and volv6.priority_adjustment.priority_adjustment_value != 0
):
change_prio = True
newval = 0
else:
newval = volv6.priority_adjustment.priority_adjustment_value
volumepatch = flasharray.VolumePatch(
priority_adjustment=flasharray.PriorityAdjustment(
priority_adjustment_operator=newop,
priority_adjustment_value=newval,
)
)
if change_prio and not module.check_mode:
changed = True
prio_res = arrayv6.patch_volumes(
names=[module.params["name"]], volume=volumepatch
)
if prio_res.status_code != 200:
module.fail_json(
msg="Failed to change DMM Priority Adjustment for {0}. Error: {1}".format(
module.params["name"], prio_res.errors[0].message
)
)
else:
if not volfact:
volfact = array.get_volume(module.params["name"])
volfact["priority_operator"] = module.params["priority_operator"]
volfact["priority_value"] = module.params["priority_value"]
if not changed:
volfact = array.get_volume(module.params["name"])
module.exit_json(changed=changed, volume=volfact)
def rename_volume(module, array):
"""Rename volume within a container, ie pod, vgroup or local array"""
volfact = []
changed = False
pod_name = ""
vgroup_name = ""
target_name = module.params["rename"]
target_exists = False
if "::" in module.params["name"]:
pod_name = module.params["name"].split("::")[0]
target_name = pod_name + "::" + module.params["rename"]
try:
array.get_volume(target_name, pending=True)
target_exists = True
except Exception:
target_exists = False
elif "/" in module.params["name"]:
vgroup_name = module.params["name"].split("/")[0]
target_name = vgroup_name + "/" + module.params["rename"]
try:
array.get_volume(target_name, pending=True)
target_exists = True
except Exception:
target_exists = False
else:
try:
array.get_volume(target_name, pending=True)
target_exists = True
except Exception:
target_exists = False
if target_exists and get_endpoint(target_name, array):
module.fail_json(
msg="Target volume {0} is a protocol-endpoinnt".format(target_name)
)
if not target_exists:
if get_destroyed_endpoint(target_name, array):
module.fail_json(
msg="Target volume {0} is a destroyed protocol-endpoinnt".format(
target_name
)
)
else:
changed = True
if not module.check_mode:
try:
volfact = array.rename_volume(
module.params["name"], module.params["rename"]
)
except Exception:
module.fail_json(
msg="Rename volume {0} to {1} failed.".format(
module.params["name"], module.params["rename"]
)
)
else:
module.fail_json(msg="Target volume {0} already exists.".format(target_name))
module.exit_json(changed=changed, volume=volfact)
def move_volume(module, array):
"""Move volume between pods, vgroups or local array"""
volfact = []
changed = vgroup_exists = target_exists = pod_exists = False
api_version = array._list_available_rest_versions()
pod_name = ""
vgroup_name = ""
volume_name = module.params["name"]
if "::" in module.params["name"]:
volume_name = module.params["name"].split("::")[1]
pod_name = module.params["name"].split("::")[0]
if "/" in module.params["name"]:
volume_name = module.params["name"].split("/")[1]
vgroup_name = module.params["name"].split("/")[0]
if module.params["move"] == "local":
target_location = ""
if "::" not in module.params["name"]:
if "/" not in module.params["name"]:
module.fail_json(
msg="Source and destination [local] cannot be the same."
)
try:
target_exists = array.get_volume(volume_name, pending=True)
except Exception:
target_exists = False
if target_exists:
module.fail_json(msg="Target volume {0} already exists".format(volume_name))
else:
try:
pod_exists = array.get_pod(module.params["move"])
if len(pod_exists["arrays"]) > 1:
module.fail_json(msg="Volume cannot be moved into a stretched pod")
if pod_exists["link_target_count"] != 0:
module.fail_json(msg="Volume cannot be moved into a linked source pod")
if PROMOTE_API_VERSION in api_version:
if pod_exists["promotion_status"] == "demoted":
module.fail_json(msg="Volume cannot be moved into a demoted pod")
pod_exists = bool(pod_exists)
except Exception:
pod_exists = False
if pod_exists:
try:
target_exists = bool(
array.get_volume(
module.params["move"] + "::" + volume_name, pending=True
)
)
except Exception:
target_exists = False
try:
vgroup_exists = bool(array.get_vgroup(module.params["move"]))
except Exception:
vgroup_exists = False
if vgroup_exists:
try:
target_exists = bool(
array.get_volume(
module.params["move"] + "/" + volume_name, pending=True
)
)
except Exception:
target_exists = False
if target_exists:
module.fail_json(msg="Volume of same name already exists in move location")
if pod_exists and vgroup_exists:
module.fail_json(
msg="Move location {0} matches both a pod and a vgroup. Please rename one of these.".format(
module.params["move"]
)
)
if not pod_exists and not vgroup_exists:
module.fail_json(
msg="Move location {0} does not exist.".format(module.params["move"])
)
if "::" in module.params["name"]:
pod = array.get_pod(module.params["move"])
if len(pod["arrays"]) > 1:
module.fail_json(msg="Volume cannot be moved out of a stretched pod")
if pod["linked_target_count"] != 0:
module.fail_json(
msg="Volume cannot be moved out of a linked source pod"
)
if PROMOTE_API_VERSION in api_version:
if pod["promotion_status"] == "demoted":
module.fail_json(msg="Volume cannot be moved out of a demoted pod")
if "/" in module.params["name"]:
if (
vgroup_name == module.params["move"]
or pod_name == module.params["move"]
):
module.fail_json(msg="Source and destination cannot be the same")
target_location = module.params["move"]
if get_endpoint(target_location, array):
module.fail_json(
msg="Target volume {0} is a protocol-endpoinnt".format(target_location)
)
changed = True
if not module.check_mode:
try:
volfact = array.move_volume(module.params["name"], target_location)
except Exception:
if target_location == "":
target_location = "[local]"
module.fail_json(
msg="Move of volume {0} to {1} failed.".format(
module.params["name"], target_location
)
)
module.exit_json(changed=changed, volume=volfact)
def delete_volume(module, array):
"""Delete Volume"""
changed = True
volfact = []
if not module.check_mode:
try:
array.destroy_volume(module.params["name"])
if module.params["eradicate"]:
try:
volfact = array.eradicate_volume(module.params["name"])
except Exception:
module.fail_json(
msg="Eradicate volume {0} failed.".format(module.params["name"])
)
except Exception:
module.fail_json(
msg="Delete volume {0} failed.".format(module.params["name"])
)
module.exit_json(changed=changed, volume=volfact)
def eradicate_volume(module, array):
"""Eradicate Deleted Volume"""
changed = True
volfact = []
if not module.check_mode:
if module.params["eradicate"]:
try:
array.eradicate_volume(module.params["name"])
except Exception:
module.fail_json(
msg="Eradication of volume {0} failed".format(module.params["name"])
)
module.exit_json(changed=changed, volume=volfact)
def recover_volume(module, array):
"""Recover Deleted Volume"""
changed = True
volfact = []
if not module.check_mode:
try:
array.recover_volume(module.params["name"])
except Exception:
module.fail_json(
msg="Recovery of volume {0} failed".format(module.params["name"])
)
volfact = array.get_volume(module.params["name"])
volfact["page83_naa"] = PURE_OUI + volfact["serial"].lower()
volfact["nvme_nguid"] = _create_nguid(volfact["serial"].lower())
module.exit_json(changed=changed, volume=volfact)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(
dict(
name=dict(type="str", required=True),
target=dict(type="str"),
move=dict(type="str"),
rename=dict(type="str"),
overwrite=dict(type="bool", default=False),
eradicate=dict(type="bool", default=False),
state=dict(type="str", default="present", choices=["absent", "present"]),
bw_qos=dict(type="str", aliases=["qos"]),
iops_qos=dict(type="str"),
pgroup=dict(type="str"),
count=dict(type="int"),
start=dict(type="int", default=0),
digits=dict(type="int", default=1),
suffix=dict(type="str"),
priority_operator=dict(type="str", choices=["+", "-", "="]),
priority_value=dict(type="int", choices=[-10, 0, 10]),
size=dict(type="str"),
)
)
mutually_exclusive = [
["size", "target"],
["move", "rename", "target", "eradicate"],
["rename", "move", "target", "eradicate"],
]
required_together = [["priority_operator", "priority_value"]]
module = AnsibleModule(
argument_spec,
mutually_exclusive=mutually_exclusive,
required_together=required_together,
supports_check_mode=True,
)
size = module.params["size"]
bw_qos = module.params["bw_qos"]
iops_qos = module.params["iops_qos"]
state = module.params["state"]
destroyed = False
array = get_system(module)
volume = get_volume(module, array)
api_version = array._list_available_rest_versions()
endpoint = get_endpoint(module.params["name"], array)
if endpoint:
module.fail_json(
msg="Volume {0} is an endpoint. Use purefa_endpoint module.".format(
module.params["name"]
)
)
if module.params["pgroup"]:
pattern = re.compile("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$")
if ":" in module.params["pgroup"] and OFFLOAD_API_VERSION not in api_version:
module.fail_json(
msg="API version does not support offload protection groups."
)
if "::" in module.params["pgroup"] and POD_API_VERSION not in api_version:
module.fail_json(
msg="API version does not support ActiveCluster protection groups."
)
if ":" in module.params["pgroup"]:
if "::" in module.params["pgroup"]:
pgname = module.params["pgroup"].split("::")[1]
else:
pgname = module.params["pgroup"].split(":")[1]
if not pattern.match(pgname):
module.fail_json(
msg="Protection Group name {0} does not conform to naming convention".format(
pgname
)
)
else:
if not pattern.match(module.params["pgroup"]):
module.fail_json(
msg="Protection Group name {0} does not conform to naming convention".format(
pgname
)
)
pgroup = get_pgroup(module, array)
xpgroup = get_pending_pgroup(module, array)
if "::" in module.params["pgroup"]:
if not get_pod(module, array):
module.fail_json(
msg="Pod {0} does not exist.".format(
module.params["pgroup"].split("::")[0]
)
)
if not pgroup:
if xpgroup:
module.fail_json(
msg="Protection Group {0} is currently deleted. Please restore to use.".format(
module.params["pgroup"]
)
)
else:
module.fail_json(
msg="Protection Group {0} does not exist.".format(
module.params["pgroup"]
)
)
if not volume:
destroyed = get_destroyed_volume(module.params["name"], array)
target = get_target(module, array)
if module.params["count"]:
if not HAS_PURESTORAGE:
module.fail_json(
msg="py-pure-client sdk is required to support 'count' parameter"
)
if MULTI_VOLUME_VERSION not in api_version:
module.fail_json(
msg="'count' parameter is not supported until Purity//FA 6.0.0 or higher"
)
if module.params["digits"] and module.params["digits"] not in range(1, 10):
module.fail_json(msg="'digits' must be in the range of 1 to 10")
if module.params["start"] < 0:
module.fail_json(msg="'start' must be a positive number")
volume = get_multi_volumes(module)
if state == "present" and not volume and size:
create_multi_volume(module, array)
elif state == "present" and not volume and not size:
module.fail_json(msg="Size must be specified to create a new volume")
elif state == "absent" and not volume:
module.exit_json(changed=False)
else:
module.warn("Method not yet supported for multi-volume")
else:
if state == "present" and not volume and not destroyed and size:
create_volume(module, array)
elif state == "present" and volume and (size or bw_qos or iops_qos):
update_volume(module, array)
elif state == "present" and not volume and module.params["move"]:
module.fail_json(
msg="Volume {0} cannot be moved - does not exist (maybe deleted)".format(
module.params["name"]
)
)
elif state == "present" and volume and module.params["move"]:
move_volume(module, array)
elif state == "present" and volume and module.params["rename"]:
rename_volume(module, array)
elif (
state == "present"
and destroyed
and not module.params["move"]
and not module.params["rename"]
):
recover_volume(module, array)
elif state == "present" and destroyed and module.params["move"]:
module.fail_json(
msg="Volume {0} exists, but in destroyed state".format(
module.params["name"]
)
)
elif state == "present" and volume and target:
copy_from_volume(module, array)
elif state == "present" and volume and not target:
copy_from_volume(module, array)
elif state == "absent" and volume:
delete_volume(module, array)
elif state == "absent" and destroyed:
eradicate_volume(module, array)
elif state == "present":
if not volume and not size:
module.fail_json(msg="Size must be specified to create a new volume")
elif state == "absent" and not volume:
module.exit_json(changed=False)
module.exit_json(changed=False)
if __name__ == "__main__":
main()