Skip to content

Commit

Permalink
Initial commit for module to manage Vexata storage volumes + fixes fo…
Browse files Browse the repository at this point in the history
…rm code review in pr ansible#47091.
  • Loading branch information
vexata committed Mar 5, 2019
1 parent a8d829d commit b6fd186
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/docsite/rst/dev_guide/developing_module_utilities.rst
Expand Up @@ -82,5 +82,6 @@ Ansible ships with the following list of ``module_utils`` files. The module util
- urls.py - Utilities for working with http and https requests
- utm_utils.py - Contains base class for creating new Sophos UTM Modules and helper functions for handling the rest interface of Sophos UTM
- vca.py - Contains utilities for modules that work with VMware vCloud Air
- vexata.py - Utilities for modules that work with Vexata storage platforms.
- vmware.py - Contains utilities for modules that work with VMware vSphere VMs
- xenserver.py - Contains utilities for modules that work with XenServer.
94 changes: 94 additions & 0 deletions lib/ansible/module_utils/vexata.py
@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2018, Sandeep Kasargod <sandeep@vexata.com>
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)


HAS_VEXATAPI = True
try:
from vexatapi.vexata_api_proxy import VexataAPIProxy
except ImportError:
HAS_VEXATAPI = False

from ansible.module_utils._text import to_native
from ansible.module_utils.basic import env_fallback

VXOS_VERSION = None


def get_version(iocs_json):
if not iocs_json:
raise Exception('Invalid IOC json')
active = filter(lambda x: x['mgmtRole'], iocs_json)
if not active:
raise Exception('Unable to detect active IOC')
active = active[0]
ver = active['swVersion']
if ver[0] != 'v':
raise Exception('Illegal version string')
ver = ver[1:ver.find('-')]
ver = map(int, ver.split('.'))
return tuple(ver)


def get_array(module):
"""Return storage array object or fail"""
global VXOS_VERSION
array = module.params['array']
user = module.params.get('user', None)
password = module.params.get('password', None)
validate = module.params.get('validate_certs')

if not HAS_VEXATAPI:
module.fail_json(msg='vexatapi library is required for this module. '
'To install, use `pip install vexatapi`')

if user and password:
system = VexataAPIProxy(array, user, password, verify_cert=validate)
else:
module.fail_json(msg='The user/password are required to be passed in to '
'the module as arguments or by setting the '
'VEXATA_USER and VEXATA_PASSWORD environment variables.')
try:
if system.test_connection():
VXOS_VERSION = get_version(system.iocs())
return system
else:
module.fail_json(msg='Test connection to array failed.')
except Exception as e:
module.fail_json(msg='Vexata API access failed: {0}'.format(to_native(e)))


def argument_spec():
"""Return standard base dictionary used for the argument_spec argument in AnsibleModule"""
return dict(
array=dict(type='str',
required=True),
user=dict(type='str',
fallback=(env_fallback, ['VEXATA_USER'])),
password=dict(type='str',
no_log=True,
fallback=(env_fallback, ['VEXATA_PASSWORD'])),
validate_certs=dict(type='bool',
required=False,
default=False),
)


def required_together():
"""Return the default list used for the required_together argument to AnsibleModule"""
return [['user', 'password']]


def size_to_MiB(size):
"""Convert a '<integer>[MGT]' string to MiB, return -1 on error."""
quant = size[:-1]
exponent = size[-1]
if not quant.isdigit() or exponent not in 'MGT':
return -1
quant = int(quant)
if exponent == 'G':
quant <<= 10
elif exponent == 'T':
quant <<= 20
return quant
Empty file.
195 changes: 195 additions & 0 deletions lib/ansible/modules/storage/vexata/vexata_volume.py
@@ -0,0 +1,195 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2018, Sandeep Kasargod (sandeep@vexata.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 = '''
---
module: vexata_volume
version_added: 2.8
short_description: Manage volumes on Vexata VX100 storage arrays
description:
- Create, deletes or extend volumes on a Vexata VX100 array.
author:
- Sandeep Kasargod (@vexata)
options:
name:
description:
- Volume name.
required: true
state:
description:
- Creates/Modifies volume when present or removes when absent.
default: present
choices: [ present, absent ]
size:
description:
- Volume size in M, G, T units. M=2^20, G=2^30, T=2^40 bytes.
extends_documentation_fragment:
- vexata.vx100
'''

EXAMPLES = '''
- name: Create new 2 TiB volume named foo
vexata_volume:
name: foo
size: 2T
state: present
array: vx100_ultra.test.com
user: admin
password: secret
- name: Expand volume named foo to 4 TiB
vexata_volume:
name: foo
size: 4T
state: present
array: vx100_ultra.test.com
user: admin
password: secret
- name: Delete volume named foo
vexata_volume:
name: foo
state: absent
array: vx100_ultra.test.com
user: admin
password: secret
'''

RETURN = '''
'''

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.vexata import (
argument_spec, get_array, required_together, size_to_MiB)


def get_volume(module, array):
"""Retrieve a named volume if it exists, None if absent."""
name = module.params['name']
try:
vols = array.list_volumes()
vol = filter(lambda v: v['name'] == name, vols)
if len(vol) == 1:
return vol[0]
else:
return None
except Exception:
module.fail_json(msg='Error while attempting to retrieve volumes.')


def create_volume(module, array):
""""Create a new volume."""
changed = False
size = size_to_MiB(module.params['size'])
if size <= 0:
module.fail_json(msg='Invalid volume size, must be <integer>[MGT].')
if module.check_mode:
module.exit_json(changed=changed)

try:
vol = array.create_volume(
module.params['name'],
'Ansible volume',
size)
if vol:
module.log(msg='Created volume {0}'.format(vol['id']))
changed = True
else:
module.fail_json(msg='Volume create failed.')
except Exception:
pass
module.exit_json(changed=changed)


def update_volume(module, array, volume):
"""Expand the volume size."""
changed = False
if not module.params.get('size', False):
module.fail_json(msg='Size is required to update volume')
size = size_to_MiB(module.params['size'])
prev_size = volume['volSize']
if size <= prev_size:
module.log(msg='Volume expanded size needs to be larger '
'than current size.')
if module.check_mode:
module.exit_json(changed=changed)

try:
vol = array.grow_volume(
volume['name'],
volume['description'],
volume['id'],
size)
if vol:
changed = True
except Exception:
pass

module.exit_json(changed=changed)


def delete_volume(module, array, volume):
changed = False
vol_name = volume['name']
if module.check_mode:
module.exit_json(changed=changed)

try:
ok = array.delete_volume(
volume['id'])
if ok:
module.log(msg='Volume {0} deleted.'.format(vol_name))
changed = True
else:
raise Exception
except Exception:
pass
module.exit_json(changed=changed)


def main():
arg_spec = argument_spec()
arg_spec.update(
dict(
name=dict(type='str', required=True),
state=dict(default='present', choices=['present', 'absent']),
size=dict()
)
)

module = AnsibleModule(arg_spec,
supports_check_mode=True,
required_together=required_together())

state = module.params['state']
size = module.params['size']
if size and size_to_MiB(size) < 0:
module.fail_json(msg='Volume size should be in M, G, T units')

array = get_array(module)
volume = get_volume(module, array)

if state == 'present' and size and not volume:
create_volume(module, array)
elif state == 'present' and size and volume:
update_volume(module, array, volume)
elif state == 'absent' and volume:
delete_volume(module, array, volume)
else:
module.exit_json(changed=False)


if __name__ == '__main__':
main()
46 changes: 46 additions & 0 deletions lib/ansible/utils/module_docs_fragments/vexata.py
@@ -0,0 +1,46 @@
#
# Copyright: (c) 2018, Sandeep Kasargod <sandeep@vexata.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)


class ModuleDocFragment(object):

DOCUMENTATION = """
options:
- See respective platform section for more details
requirements:
- See respective platform section for more details
notes:
- Ansible modules are available for Vexata VX100 arrays.
"""

# Documentation fragment for Vexata VX100 series
VX100 = '''
options:
array:
description:
- Vexata VX100 array hostname or IPv4 Address.
required: true
user:
description:
- Vexata API user with administrative privileges.
required: false
password:
description:
- Vexata API user password.
required: false
validate_certs:
description:
- Allows connection when SSL certificates are not valid. Set to C(false) when certificates are not trusted.
- If set to C(yes), please make sure Python >= 2.7.9 is installed on the given machine.
required: false
type: bool
default: 'no'
requirements:
- Vexata VX100 storage array with VXOS >= v3.5.0 on storage array
- "vexatapi >= 0.0.1"
- "python >= 2.7"
- VEXATA_USER and VEXATA_PASSWORD environment variables must be set if
user and password arguments are not passed to the module directly.
'''

0 comments on commit b6fd186

Please sign in to comment.