Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New module for managing volumes in Vexata storage arrays. (redo of PR #47091) #49006

Merged
merged 5 commits into from
Apr 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docsite/rst/dev_guide/developing_module_utilities.rst
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2019, 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.
201 changes: 201 additions & 0 deletions lib/ansible/modules/storage/vexata/vexata_volume.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2019, 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 = r'''
---
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
type: str
state:
description:
- Creates/Modifies volume when present or removes when absent.
default: present
choices: [ present, absent ]
type: str
size:
description:
- Volume size in M, G, T units. M=2^20, G=2^30, T=2^40 bytes.
type: str
extends_documentation_fragment:
- vexata.vx100
'''

EXAMPLES = r'''
- 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 = r'''
'''

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 validate_size(module, err_msg):
size = module.params.get('size', False)
if not size:
module.fail_json(msg=err_msg)
size = size_to_MiB(size)
if size <= 0:
module.fail_json(msg='Invalid volume size, must be <integer>[MGT].')
return size


def create_volume(module, array):
""""Create a new volume."""
changed = False
vexata marked this conversation as resolved.
Show resolved Hide resolved
size = validate_size(module, err_msg='Size is required to create volume.')
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
size = validate_size(module, err_msg='Size is required to update volume')
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(type='str')
)
)

module = AnsibleModule(arg_spec,
supports_check_mode=True,
required_together=required_together())
vexata marked this conversation as resolved.
Show resolved Hide resolved

state = module.params['state']
array = get_array(module)
volume = get_volume(module, array)

if state == 'present':
if not volume:
create_volume(module, array)
else:
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()
49 changes: 49 additions & 0 deletions lib/ansible/plugins/doc_fragments/vexata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#
# Copyright: (c) 2019, 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 = r'''
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 = r'''
options:
array:
description:
- Vexata VX100 array hostname or IPv4 Address.
required: true
type: str
user:
description:
- Vexata API user with administrative privileges.
required: false
type: str
password:
description:
- Vexata API user password.
required: false
type: str
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.
'''