forked from ansible/ansible
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit for module to manage Vexata storage volumes + fixes fo…
…rm code review in pr ansible#47091.
- Loading branch information
Showing
5 changed files
with
336 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. | ||
''' |