Repository URL to install this package:
|
Version:
6.0.0 ▾
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Infinidat <info@infinidat.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: infini_vol
version_added: 2.3
short_description: Create, Delete or Modify volumes on Infinibox
description:
- This module creates, deletes or modifies a volume on Infinibox.
author: Gregory Shulov (@GR360RY)
options:
name:
description:
- Volume Name
required: true
state:
description:
- Creates/Modifies master volume or snapshot when present or removes when absent.
required: false
default: present
choices: [ "stat", "present", "absent" ]
size:
description:
- Volume size in MB, GB or TB units. Required for creating a master volume, but not a snapshot
required: false
snapshot_lock_expires_at:
description:
- This will cause a snapshot to be locked at the specified date-time.
Uses python's datetime format YYYY-mm-dd HH:MM:SS.ffffff, e.g. 2020-02-13 16:21:59.699700
required: false
snapshot_lock_only:
description:
- This will lock an existing snapshot but will suppress refreshing the snapshot.
type: bool
required: false
default: false
thin_provision:
description:
- Whether the master volume should be thin provisioned. Required for creating a master volume, but not a snapshot.
type: bool
required: false
default: true
version_added: '2.8'
pool:
description:
- Pool that master volume will reside within. Required for creating a master volume, but not a snapshot.
required: false
volume_type:
description:
- Specifies the volume type, regular volume or snapshot.
required: false
default: master
choices: [ "master", "snapshot" ]
write_protected:
description:
- Specifies if the volume should be write protected. Default will be True for snapshots, False for regular volumes.
required: false
default: "Default"
choices: ["Default", "True", "False"]
version_added: '2.10'
parent_volume_name:
description:
- Specify a volume name. This is the volume parent for creating a snapshot. Required if volume_type is snapshot.
required: false
extends_documentation_fragment:
- infinibox
requirements:
- capacity
'''
EXAMPLES = r'''
- name: Create new volume named foo under pool named bar
infini_vol:
name: foo
# volume_type: master # Default
size: 1TB
thin_provision: yes
pool: bar
state: present
user: admin
password: secret
system: ibox001
- name: Create snapshot named foo_snap from volume named foo
infini_vol:
name: foo_snap
volume_type: snapshot
parent_volume_name: foo
state: present
user: admin
password: secret
system: ibox001
- name: Stat snapshot, also a volume, named foo_snap
infini_vol:
name: foo_snap
state: present
user: admin
password: secret
system: ibox001
- name: Remove snapshot, also a volume, named foo_snap
infini_vol:
name: foo_snap
state: absent
user: admin
password: secret
system: ibox001
'''
# RETURN = r''' # '''
try:
from capacity import KiB, Capacity
HAS_CAPACITY = True
except ImportError:
HAS_CAPACITY = False
import arrow
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.infinidat.infinibox.plugins.module_utils.infinibox import \
HAS_INFINISDK, api_wrapper, infinibox_argument_spec, ObjectNotFound, \
get_pool, get_system, get_volume, get_vol_sn
@api_wrapper
def create_volume(module, system):
"""Create Volume"""
if not module.check_mode:
if module.params['thin_provision']:
prov_type = 'THIN'
else:
prov_type = 'THICK'
pool = get_pool(module, system)
volume = system.volumes.create(name=module.params['name'], provtype=prov_type, pool=pool)
if module.params['size']:
size = Capacity(module.params['size']).roundup(64 * KiB)
volume.update_size(size)
if module.params['write_protected'] is not None:
is_write_prot = volume.is_write_protected()
desired_is_write_prot = module.params['write_protected']
if is_write_prot != desired_is_write_prot:
volume.update_field('write_protected', desired_is_write_prot)
changed = True
return changed
@api_wrapper
def update_volume(module, volume):
"""Update Volume"""
changed = False
if module.params['size']:
size = Capacity(module.params['size']).roundup(64 * KiB)
if volume.get_size() != size:
if not module.check_mode:
volume.update_size(size)
changed = True
if module.params['thin_provision'] is not None:
type = str(volume.get_provisioning())
if type == 'THICK' and module.params['thin_provision']:
if not module.check_mode:
volume.update_provisioning('THIN')
changed = True
if type == 'THIN' and not module.params['thin_provision']:
if not module.check_mode:
volume.update_provisioning('THICK')
changed = True
if module.params['write_protected'] is not None:
is_write_prot = volume.is_write_protected()
desired_is_write_prot = module.params['write_protected']
if is_write_prot != desired_is_write_prot:
volume.update_field('write_protected', desired_is_write_prot)
return changed
@api_wrapper
def delete_volume(module, volume):
""" Delete Volume. Volume could be a snapshot."""
if not module.check_mode:
volume.delete()
changed = True
return True
@api_wrapper
def create_snapshot(module, system):
"""Create Snapshot from parent volume"""
snapshot_name = module.params['name']
parent_volume_name = module.params['parent_volume_name']
try:
parent_volume = system.volumes.get(name=parent_volume_name)
except ObjectNotFound as err:
msg = 'Cannot create snapshot {}. Parent volume {} not found'.format(
snapshot_name,
parent_volume_name)
module.fail_json(msg=msg)
if not parent_volume:
msg = "Cannot find new snapshot's parent volume named {}".format(parent_volume_name)
module.fail_json(msg=msg)
if not module.check_mode:
if module.params['snapshot_lock_only']:
msg = "Snapshot does not exist. Cannot comply with 'snapshot_lock_only: true'."
module.fail_json(msg=msg)
check_snapshot_lock_options(module)
snapshot = parent_volume.create_snapshot(name=snapshot_name)
if module.params['write_protected'] is not None:
is_write_prot = snapshot.is_write_protected()
desired_is_write_prot = module.params['write_protected']
if is_write_prot != desired_is_write_prot:
snapshot.update_field('write_protected', desired_is_write_prot)
manage_snapshot_locks(module, snapshot)
changed = True
return changed
@api_wrapper
def update_snapshot(module, snapshot):
"""
Update/refresh snapshot. May also lock it.
"""
refresh_changed = False
if not module.params['snapshot_lock_only']:
snap_is_locked = snapshot.get_lock_state() == "LOCKED"
if not snap_is_locked:
if not module.check_mode:
snapshot.refresh_snapshot()
refresh_changed = True
else:
msg = "Snapshot is locked and may not be refreshed"
module.fail_json(msg=msg)
check_snapshot_lock_options(module)
lock_changed = manage_snapshot_locks(module, snapshot)
if not module.check_mode:
if module.params['write_protected'] is not None:
is_write_prot = snapshot.is_write_protected()
desired_is_write_prot = module.params['write_protected']
if is_write_prot != desired_is_write_prot:
snapshot.update_field('write_protected', desired_is_write_prot)
return refresh_changed or lock_changed
def get_sys_pool_vol_parname(module):
system = get_system(module)
pool = get_pool(module, system)
if module.params['name']:
volume = get_volume(module, system)
else:
volume = get_vol_sn(module, system)
parname = module.params['parent_volume_name']
return (system, pool, volume, parname)
def check_snapshot_lock_options(module):
"""
Check if specified options are feasible for a snapshot.
Prevent very long lock times.
max_delta_minutes limits locks to 30 days (43200 minutes).
This functionality is broken out from manage_snapshot_locks() to allow
it to be called by create_snapshot() before the snapshot is actually
created.
"""
snapshot_lock_expires_at = module.params['snapshot_lock_expires_at']
if snapshot_lock_expires_at: # Then user has specified wish to lock snap
lock_expires_at = arrow.get(snapshot_lock_expires_at)
# Check for lock in the past
now = arrow.utcnow()
if lock_expires_at <= now:
msg = "Cannot lock snapshot with a snapshot_lock_expires_at "
msg += "of '{}' from the past".format(snapshot_lock_expires_at)
module.fail_json(msg=msg)
# Check for lock later than max lock, i.e. too far in future.
max_delta_minutes = 43200 # 30 days in minutes
max_lock_expires_at = now.shift(minutes=max_delta_minutes)
if lock_expires_at >= max_lock_expires_at:
msg = "snapshot_lock_expires_at exceeds {} days in the future".format(
max_delta_minutes//24//60)
module.fail_json(msg=msg)
def manage_snapshot_locks(module, snapshot):
"""
Manage the locking of a snapshot. Check for bad lock times.
See check_snapshot_lock_options() which has additional checks.
"""
name = module.params["name"]
snapshot_lock_expires_at = module.params['snapshot_lock_expires_at']
snap_is_locked = snapshot.get_lock_state() == "LOCKED"
current_lock_expires_at = snapshot.get_lock_expires_at()
changed = False
check_snapshot_lock_options(module)
if snapshot_lock_expires_at: # Then user has specified wish to lock snap
lock_expires_at = arrow.get(snapshot_lock_expires_at)
if snap_is_locked and lock_expires_at < current_lock_expires_at:
# Lock earlier than current lock
msg = "snapshot_lock_expires_at '{}' preceeds the current lock time of '{}'".format(
lock_expires_at,
current_lock_expires_at)
module.fail_json(msg=msg)
elif snap_is_locked and lock_expires_at == current_lock_expires_at:
# Lock already set to correct time
pass
else:
# Set lock
if not module.check_mode:
snapshot.update_lock_expires_at(lock_expires_at)
changed = True
return changed
def handle_stat(module):
system, pool, volume, parname = get_sys_pool_vol_parname(module)
if not volume:
msg = "Volume {} not found. Cannot stat.".format(module.params['name'])
module.fail_json(msg=msg)
fields = volume.get_fields() #from_cache=True, raw_value=True)
created_at = str(fields.get('created_at', None))
has_children = fields.get('has_children', None)
lock_expires_at= str(volume.get_lock_expires_at())
lock_state = volume.get_lock_state()
mapped = str(fields.get('mapped', None))
name = fields.get('name', None)
parent_id = fields.get('parent_id', None)
serial = str(volume.get_serial())
size = str(volume.get_size())
updated_at = str(fields.get('updated_at', None))
used = str(fields.get('used_size', None))
volume_id = fields.get('id', None)
volume_type = fields.get('type', None)
write_protected = fields.get('write_protected', None)
if volume_type == 'SNAPSHOT':
msg = 'Snapshot stat found'
else:
msg = 'Volume stat found'
result = dict(
changed=False,
name=name,
created_at=created_at,
has_children=has_children,
lock_expires_at=lock_expires_at,
lock_state=lock_state,
mapped=mapped,
msg=msg,
parent_id=parent_id,
serial=serial,
size=size,
updated_at=updated_at,
used=used,
volume_id=volume_id,
volume_type=volume_type,
write_protected=write_protected,
)
module.exit_json(**result)
def handle_present(module):
system, pool, volume, parname = get_sys_pool_vol_parname(module)
volume_type = module.params['volume_type']
if volume_type == 'master':
if not volume:
changed = create_volume(module, system)
module.exit_json(changed=changed, msg="Volume created")
else:
changed = update_volume(module, volume)
module.exit_json(changed=changed, msg="Volume updated")
elif volume_type == 'snapshot':
snapshot = volume
if not snapshot:
changed = create_snapshot(module, system)
module.exit_json(changed=changed, msg="Snapshot created")
else:
changed = update_snapshot(module, snapshot)
module.exit_json(changed=changed, msg="Snapshot updated")
else:
module.fail_json(msg='A programming error has occurred')
def handle_absent(module):
system, pool, volume, parname = get_sys_pool_vol_parname(module)
volume_type = module.params['volume_type']
if volume and volume.get_lock_state() == "LOCKED":
msg = "Cannot delete snapshot. Locked."
module.fail_json(msg=msg)
if volume_type == 'master':
if not volume:
module.exit_json(changed=False, msg="Volume already absent")
else:
changed = delete_volume(module, volume)
module.exit_json(changed=changed, msg="Volume removed")
elif volume_type == 'snapshot':
if not volume:
module.exit_json(changed=False, msg="Snapshot already absent")
else:
snapshot = volume
changed = delete_volume(module, snapshot)
module.exit_json(changed=changed, msg="Snapshot removed")
else:
module.fail_json(msg='A programming error has occured')
def execute_state(module):
# Handle different write_protected defaults depending on volume_type.
if module.params['volume_type'] == "snapshot":
if module.params['write_protected'] == 'Default':
module.params['write_protected'] = True
elif module.params['volume_type'] == "master":
if module.params['write_protected'] == 'Default':
module.params['write_protected'] = False
else:
msg = "A programming error has occurred handling volume type value"
module.fail_json(msg=msg)
assert module.params['write_protected'] in [True, False]
state = module.params['state']
try:
if state == 'stat':
handle_stat(module)
elif state == 'present':
handle_present(module)
elif state == 'absent':
handle_absent(module)
else:
module.fail_json(msg='Internal handler error. Invalid state: {0}'.format(state))
finally:
system = get_system(module)
system.logout()
def check_options(module):
"""Verify module options are sane"""
state = module.params['state']
size = module.params['size']
pool = module.params['pool']
volume_type = module.params['volume_type']
parent_volume_name = module.params['parent_volume_name']
if state == 'present':
if volume_type == 'master':
if state == 'present':
if parent_volume_name:
msg = "parent_volume_name should not be specified "
msg += "if volume_type is 'volume'. Snapshots only."
module.fail_json(msg=msg)
if not size:
msg = "Size is required to create a volume"
module.fail_json(msg=msg)
elif volume_type == "snapshot":
if size or pool:
msg = "Neither pool nor size should not be specified "
msg += "for volume_type snapshot"
module.fail_json(msg=msg)
if state == "present":
if not parent_volume_name:
msg = "For state 'present' and volume_type 'snapshot', "
msg += "parent_volume_name is required"
module.fail_json(msg=msg)
else:
msg = "A programming error has occurred"
module.fail_json(msg=msg)
def main():
argument_spec = infinibox_argument_spec()
argument_spec.update(
dict(
name=dict(required=False),
parent_volume_name=dict(required=False),
pool=dict(required=False),
size=dict(),
serial=dict(),
snapshot_lock_expires_at=dict(),
snapshot_lock_only=dict(type='bool', default=False),
state=dict(default='present', choices=['stat', 'present', 'absent']),
thin_provision=dict(type='bool', default=True),
write_protected=dict(default='Default', choices=['Default', 'True', 'False']),
volume_type=dict(default='master', choices=['master', 'snapshot']),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
if not HAS_INFINISDK:
module.fail_json(msg=missing_required_lib('infinisdk'))
if module.params['size']:
try:
Capacity(module.params['size'])
except Exception:
module.fail_json(msg='size (Physical Capacity) should be defined in MB, GB, TB or PB units')
check_options(module)
execute_state(module)
if __name__ == '__main__':
main()