From 972a27b14e83a3e50a8c3d15dd589007295448db Mon Sep 17 00:00:00 2001 From: "Christopher H. Laco" Date: Fri, 8 Nov 2013 19:29:41 -0600 Subject: [PATCH 1/2] Add Rackspace Cloud Block Storage modules - Add rax_cbs to create/delete cloud block storage volumes - Add rax_cbs_attachments to attach/detach volumes from servers --- library/cloud/rax_cbs | 241 ++++++++++++++++++++++++++++ library/cloud/rax_cbs_attachments | 255 ++++++++++++++++++++++++++++++ 2 files changed, 496 insertions(+) create mode 100644 library/cloud/rax_cbs create mode 100644 library/cloud/rax_cbs_attachments diff --git a/library/cloud/rax_cbs b/library/cloud/rax_cbs new file mode 100644 index 00000000000..efa40c0ffc0 --- /dev/null +++ b/library/cloud/rax_cbs @@ -0,0 +1,241 @@ +#!/usr/bin/python -tt +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +DOCUMENTATION = ''' +--- +module: rax_cbs +short_description: Manipulate Rackspace Cloud Block Storage Volumes +description: + - Manipulate Rackspace Cloud Block Storage Volumes +version_added: "1.5" +options: + api_key: + description: + - Rackspace API key (overrides C(credentials)) + credentials: + description: + - File to find the Rackspace credentials in (ignored if C(api_key) and + C(username) are provided) + default: null + aliases: ['creds_file'] + description: + description: + - Description to give the volume being created + default: null + meta: + description: + - A hash of metadata to associate with the volume + default: null + name: + description: + - Name to give the volume being created + default: null + required: true + region: + description: + - Region to create the volume in + default: DFW + size: + description: + - Size of the volume to create in Gigabytes + default: 100 + required: true + snapshot_id: + description: + - The id of the snapshot to create the volume from + default: null + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + required: true + volume_type: + description: + - Type of the volume being created + choices: ['SATA', 'SSD'] + default: SATA + required: true + username: + description: + - Rackspace username (overrides C(credentials)) + wait: + description: + - wait for the volume to be in state 'available' before returning + default: "no" + choices: [ "yes", "no" ] + wait_timeout: + description: + - how long before wait gives up, in seconds + default: 300 +requirements: [ "pyrax" ] +author: Christopher H. Laco, Matt Martz +notes: + - The following environment variables can be used, C(RAX_USERNAME), + C(RAX_API_KEY), C(RAX_CREDS_FILE), C(RAX_CREDENTIALS), C(RAX_REGION). + - C(RAX_CREDENTIALS) and C(RAX_CREDS_FILE) points to a credentials file + appropriate for pyrax. See U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating) + - C(RAX_USERNAME) and C(RAX_API_KEY) obviate the use of a credentials file + - C(RAX_REGION) defines a Rackspace Public Cloud region (DFW, ORD, LON, ...) +''' + +EXAMPLES = ''' +- name: Build a Block Storage Volume + gather_facts: False + hosts: local + connection: local + tasks: + - name: Storage volume create request + local_action: + module: rax_cbs + credentials: ~/.raxpub + name: my-volume + description: My Volume + volume_type: SSD + size: 150 + region: DFW + wait: yes + state: present + meta: + app: my-cool-app + register: my_volume +''' + +import sys + +from types import NoneType + +try: + import pyrax +except ImportError: + print("failed=True msg='pyrax required for this module'") + sys.exit(1) + +NON_CALLABLES = (basestring, bool, dict, int, list, NoneType) +VOLUME_STATUS = ('available', 'attaching', 'creating', 'deleting', 'in-use', + 'error', 'error_deleting') + + +def cloud_block_storage(module, state, name, description, meta, size, + snapshot_id, volume_type, wait, wait_timeout): + for arg in (state, name, size, volume_type): + if not arg: + module.fail_json(msg='%s is required for rax_clb' % arg) + + if int(size) < 100: + module.fail_json(msg='"size" must be greater than or equal to 100') + + changed = False + volumes = [] + instance = {} + + cbs = pyrax.cloud_blockstorage + + for volume in cbs.list(): + if name != volume.name and name != volume.id: + continue + + volumes.append(volume) + + if len(volumes) > 1: + module.fail_json(msg='Multiple Storage Volumes were matched by name, ' + 'try using the Volume ID instead') + + if state == 'present': + if not volumes: + try: + volume = cbs.create(name, size=size, volume_type=volume_type, + description=description, + metadata=meta, + snapshot_id=snapshot_id) + changed = True + except Exception, e: + module.fail_json(msg='%s' % e.message) + else: + volume = volumes[0] + + volume.get() + for key, value in vars(volume).iteritems(): + if (isinstance(value, NON_CALLABLES) and + not key.startswith('_')): + instance[key] = value + + result = dict(changed=changed, volume=instance) + + if volume.status == 'error': + result['msg'] = '%s failed to build' % volume.id + elif wait and volume.status not in VOLUME_STATUS: + result['msg'] = 'Timeout waiting on %s' % volume.id + + if 'msg' in result: + module.fail_json(**result) + else: + module.exit_json(**result) + + elif state == 'absent': + if volumes: + volume = volumes[0] + try: + volume.delete() + changed = True + except Exception, e: + module.fail_json(msg='%s' % e.message) + + module.exit_json(changed=changed, volume=instance) + + +def main(): + argument_spec = rax_argument_spec() + argument_spec.update( + dict( + description=dict(), + meta=dict(type='dict', default={}), + name=dict(), + size=dict(type='int', default=100), + snapshot_id=dict(), + state=dict(default='present', choices=['present', 'absent']), + volume_type=dict(choices=['SSD', 'SATA'], default='SATA'), + wait=dict(type='bool'), + wait_timeout=dict(type='int', default=300) + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=rax_required_together() + ) + + description = module.params.get('description') + meta = module.params.get('meta') + name = module.params.get('name') + size = module.params.get('size') + snapshot_id = module.params.get('snapshot_id') + state = module.params.get('state') + volume_type = module.params.get('volume_type') + wait = module.params.get('wait') + wait_timeout = int(module.params.get('wait_timeout')) + + setup_rax_module(module, pyrax) + + cloud_block_storage(module, state, name, description, meta, size, + snapshot_id, volume_type, wait, wait_timeout) + +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.rax import * + +### invoke the module +main() diff --git a/library/cloud/rax_cbs_attachments b/library/cloud/rax_cbs_attachments new file mode 100644 index 00000000000..2a0ac49775e --- /dev/null +++ b/library/cloud/rax_cbs_attachments @@ -0,0 +1,255 @@ +#!/usr/bin/python -tt +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +DOCUMENTATION = ''' +--- +module: rax_cbs_attachments +short_description: Manipulate Rackspace Cloud Block Storage Volume Attachments +description: + - Manipulate Rackspace Cloud Block Storage Volume Attachments +version_added: "1.5" +options: + api_key: + description: + - Rackspace API key (overrides C(credentials)) + credentials: + description: + - File to find the Rackspace credentials in (ignored if C(api_key) and + C(username) are provided) + default: null + aliases: ['creds_file'] + mountpoint: + description: + - The mount point to attach the volume to + default: null + required: true + name: + description: + - Name or id of the volume to attach/detach + default: null + required: true + region: + description: + - Region the volume and server are located in + default: DFW + server: + description: + - Name or id of the server to attach/detach + default: null + required: true + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + required: true + username: + description: + - Rackspace username (overrides C(credentials)) + wait: + description: + - wait for the volume to be in 'in-use'/'available' state before returning + default: "no" + choices: [ "yes", "no" ] + wait_timeout: + description: + - how long before wait gives up, in seconds + default: 300 +requirements: [ "pyrax" ] +author: Christopher H. Laco, Matt Martz +notes: + - The following environment variables can be used, C(RAX_USERNAME), + C(RAX_API_KEY), C(RAX_CREDS_FILE), C(RAX_CREDENTIALS), C(RAX_REGION). + - C(RAX_CREDENTIALS) and C(RAX_CREDS_FILE) points to a credentials file + appropriate for pyrax. See U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating) + - C(RAX_USERNAME) and C(RAX_API_KEY) obviate the use of a credentials file + - C(RAX_REGION) defines a Rackspace Public Cloud region (DFW, ORD, LON, ...) +''' + +EXAMPLES = ''' +- name: Attach a Block Storage Volume + gather_facts: False + hosts: local + connection: local + tasks: + - name: Storage volume attach request + local_action: + module: rax_cbs_attachments + credentials: ~/.raxpub + name: my-volume + server: my-server + mountpoint: /dev/xvdd + region: DFW + wait: yes + state: present + register: my_volume +''' + +import sys + +from types import NoneType + +try: + import pyrax +except ImportError: + print("failed=True msg='pyrax required for this module'") + sys.exit(1) + +NON_CALLABLES = (basestring, bool, dict, int, list, NoneType) +VOLUME_STATUS = ('available', 'attaching', 'creating', 'deleting', 'in-use', + 'error', 'error_deleting') + + +def cloud_block_storage_attachments(module, state, name, server, mountpoint, + wait, wait_timeout): + for arg in (state, name, server, mountpoint): + if not arg: + module.fail_json(msg='%s is required for rax_clb_attachments' % arg) + + cbs = pyrax.cloud_blockstorage + cs = pyrax.cloudservers + changed = False + volumes = [] + instance = {} + + for volume in cbs.list(): + if name == volume.display_name or name == volume.id: + volumes.append(volume) + + if len(volumes) > 1: + module.fail_json(msg='Multiple Storage Volumes were matched by name, ' + 'try using the Volume ID instead') + elif not volumes: + module.fail_json(msg='No Storage Volumes were matched by name, ' + 'try using the Volume ID instead') + + volume = volumes[0] + if state == 'present': + server = cs.servers.get(server) + + if not server: + module.fail_json(msg='No Server was matched by name, ' + 'try using the Server ID instead') + else: + if volume.attachments and volume.attachments[0]['server_id'] == server.id: + changed = False + elif volume.attachments: + module.fail_json(msg='Volume is attached to another server') + else: + try: + volume.attach_to_instance(server, mountpoint=mountpoint) + changed = True + except Exception, e: + module.fail_json(msg='%s' % e.message) + + volume.get() + + for key, value in vars(volume).iteritems(): + if (isinstance(value, NON_CALLABLES) and + not key.startswith('_')): + instance[key] = value + + result = dict(changed=changed, volume=instance) + + if volume.status == 'error': + result['msg'] = '%s failed to build' % volume.id + elif wait: + pyrax.utils.wait_until(volume, 'status', 'in-use', + interval=3, attempts=0, + verbose=False) + + if 'msg' in result: + module.fail_json(**result) + else: + module.exit_json(**result) + + elif state == 'absent': + server = cs.servers.get(server) + + if not server: + module.fail_json(msg='No Server was matched by name, ' + 'try using the Server ID instead') + else: + if volume.attachments and volume.attachments[0]['server_id'] == server.id: + try: + volume.detach() + if wait: + pyrax.utils.wait_until(volume, 'status', 'available', + interval=3, attempts=0, + verbose=False) + changed = True + except Exception, e: + module.fail_json(msg='%s' % e.message) + + volume.get() + changed = True + elif volume.attachments: + module.fail_json(msg='Volume is attached to another server') + + for key, value in vars(volume).iteritems(): + if (isinstance(value, NON_CALLABLES) and + not key.startswith('_')): + instance[key] = value + + result = dict(changed=changed, volume=instance) + + if volume.status == 'error': + result['msg'] = '%s failed to build' % volume.id + + if 'msg' in result: + module.fail_json(**result) + else: + module.exit_json(**result) + + module.exit_json(changed=changed, volume=instance) + + +def main(): + argument_spec = rax_argument_spec() + argument_spec.update( + dict( + mountpoint=dict(), + name=dict(), + server=dict(), + state=dict(default='present', choices=['present', 'absent']), + wait=dict(type='bool'), + wait_timeout=dict(type='int', default=300) + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=rax_required_together() + ) + + mountpoint = module.params.get('mountpoint') + name = module.params.get('name') + server = module.params.get('server') + state = module.params.get('state') + wait = module.params.get('wait') + wait_timeout = int(module.params.get('wait_timeout')) + + setup_rax_module(module, pyrax) + + cloud_block_storage_attachments(module, state, name, server, mountpoint, + wait, wait_timeout) + +# import module snippets +from ansible.module_utils.basic import * +from ansible.module_utils.rax import * + +### invoke the module +main() From bb8644352886b34922355f9c2bf69a6583d5935d Mon Sep 17 00:00:00 2001 From: Matt Martz Date: Wed, 26 Mar 2014 16:25:46 -0500 Subject: [PATCH 2/2] Implement recommendations from #4864 --- library/cloud/rax_cbs | 117 ++++++++++----- library/cloud/rax_cbs_attachments | 231 +++++++++++++++++++----------- 2 files changed, 224 insertions(+), 124 deletions(-) diff --git a/library/cloud/rax_cbs b/library/cloud/rax_cbs index efa40c0ffc0..73106eb41ab 100644 --- a/library/cloud/rax_cbs +++ b/library/cloud/rax_cbs @@ -1,4 +1,4 @@ -#!/usr/bin/python -tt +#!/usr/bin/python # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -20,17 +20,48 @@ module: rax_cbs short_description: Manipulate Rackspace Cloud Block Storage Volumes description: - Manipulate Rackspace Cloud Block Storage Volumes -version_added: "1.5" +version_added: 1.6 options: api_key: description: - - Rackspace API key (overrides C(credentials)) + - Rackspace API key (overrides I(credentials)) + aliases: + - password + auth_endpoint: + description: + - The URI of the authentication service + default: https://identity.api.rackspacecloud.com/v2.0/ credentials: description: - - File to find the Rackspace credentials in (ignored if C(api_key) and - C(username) are provided) + - File to find the Rackspace credentials in (ignored if I(api_key) and + I(username) are provided) default: null - aliases: ['creds_file'] + aliases: + - creds_file + env: + description: + - Environment as configured in ~/.pyrax.cfg, + see U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#pyrax-configuration) + identity_type: + description: + - Authentication machanism to use, such as rackspace or keystone + default: rackspace + region: + description: + - Region to create an instance in + default: DFW + tenant_id: + description: + - The tenant ID used for authentication + tenant_name: + description: + - The tenant name used for authentication + username: + description: + - Rackspace username (overrides I(credentials)) + verify_ssl: + description: + - Whether or not to require SSL validation of API endpoints description: description: - Description to give the volume being created @@ -44,10 +75,6 @@ options: - Name to give the volume being created default: null required: true - region: - description: - - Region to create the volume in - default: DFW size: description: - Size of the volume to create in Gigabytes @@ -60,28 +87,32 @@ options: state: description: - Indicate desired state of the resource - choices: ['present', 'absent'] + choices: + - present + - absent default: present required: true volume_type: description: - Type of the volume being created - choices: ['SATA', 'SSD'] + choices: + - SATA + - SSD default: SATA required: true - username: - description: - - Rackspace username (overrides C(credentials)) wait: description: - wait for the volume to be in state 'available' before returning default: "no" - choices: [ "yes", "no" ] + choices: + - "yes" + - "no" wait_timeout: description: - how long before wait gives up, in seconds default: 300 -requirements: [ "pyrax" ] +requirements: + - pyrax author: Christopher H. Laco, Matt Martz notes: - The following environment variables can be used, C(RAX_USERNAME), @@ -116,13 +147,14 @@ EXAMPLES = ''' import sys +from uuid import UUID from types import NoneType try: import pyrax + HAS_PYRAX = True except ImportError: - print("failed=True msg='pyrax required for this module'") - sys.exit(1) + HAS_PYRAX = False NON_CALLABLES = (basestring, bool, dict, int, list, NoneType) VOLUME_STATUS = ('available', 'attaching', 'creating', 'deleting', 'in-use', @@ -133,29 +165,33 @@ def cloud_block_storage(module, state, name, description, meta, size, snapshot_id, volume_type, wait, wait_timeout): for arg in (state, name, size, volume_type): if not arg: - module.fail_json(msg='%s is required for rax_clb' % arg) + module.fail_json(msg='%s is required for rax_cbs' % arg) - if int(size) < 100: + if size < 100: module.fail_json(msg='"size" must be greater than or equal to 100') changed = False - volumes = [] + volume = None instance = {} cbs = pyrax.cloud_blockstorage - for volume in cbs.list(): - if name != volume.name and name != volume.id: - continue - - volumes.append(volume) + if cbs is None: + module.fail_json(msg='Failed to instantiate client. This ' + 'typically indicates an invalid region or an ' + 'incorrectly capitalized region name.') - if len(volumes) > 1: - module.fail_json(msg='Multiple Storage Volumes were matched by name, ' - 'try using the Volume ID instead') + try: + UUID(name) + volume = cbs.get(name) + except ValueError: + try: + volume = cbs.find(name=name) + except Exception, e: + module.fail_json(msg='%s' % e) if state == 'present': - if not volumes: + if not volume: try: volume = cbs.create(name, size=size, volume_type=volume_type, description=description, @@ -164,8 +200,11 @@ def cloud_block_storage(module, state, name, description, meta, size, changed = True except Exception, e: module.fail_json(msg='%s' % e.message) - else: - volume = volumes[0] + else: + if wait: + attempts = wait_timeout / 5 + pyrax.utils.wait_for_build(volume, interval=5, + attempts=attempts) volume.get() for key, value in vars(volume).iteritems(): @@ -186,8 +225,7 @@ def cloud_block_storage(module, state, name, description, meta, size, module.exit_json(**result) elif state == 'absent': - if volumes: - volume = volumes[0] + if volume: try: volume.delete() changed = True @@ -203,12 +241,12 @@ def main(): dict( description=dict(), meta=dict(type='dict', default={}), - name=dict(), + name=dict(required=True), size=dict(type='int', default=100), snapshot_id=dict(), state=dict(default='present', choices=['present', 'absent']), volume_type=dict(choices=['SSD', 'SATA'], default='SATA'), - wait=dict(type='bool'), + wait=dict(type='bool', default=False), wait_timeout=dict(type='int', default=300) ) ) @@ -218,6 +256,9 @@ def main(): required_together=rax_required_together() ) + if not HAS_PYRAX: + module.fail_json(msg='pyrax is required for this module') + description = module.params.get('description') meta = module.params.get('meta') name = module.params.get('name') @@ -226,7 +267,7 @@ def main(): state = module.params.get('state') volume_type = module.params.get('volume_type') wait = module.params.get('wait') - wait_timeout = int(module.params.get('wait_timeout')) + wait_timeout = module.params.get('wait_timeout') setup_rax_module(module, pyrax) diff --git a/library/cloud/rax_cbs_attachments b/library/cloud/rax_cbs_attachments index 2a0ac49775e..c20c03a69ea 100644 --- a/library/cloud/rax_cbs_attachments +++ b/library/cloud/rax_cbs_attachments @@ -1,4 +1,4 @@ -#!/usr/bin/python -tt +#!/usr/bin/python # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -20,31 +20,58 @@ module: rax_cbs_attachments short_description: Manipulate Rackspace Cloud Block Storage Volume Attachments description: - Manipulate Rackspace Cloud Block Storage Volume Attachments -version_added: "1.5" +version_added: 1.6 options: api_key: description: - - Rackspace API key (overrides C(credentials)) + - Rackspace API key (overrides I(credentials)) + aliases: + - password + auth_endpoint: + description: + - The URI of the authentication service + default: https://identity.api.rackspacecloud.com/v2.0/ credentials: description: - - File to find the Rackspace credentials in (ignored if C(api_key) and - C(username) are provided) + - File to find the Rackspace credentials in (ignored if I(api_key) and + I(username) are provided) default: null - aliases: ['creds_file'] - mountpoint: + aliases: + - creds_file + env: + description: + - Environment as configured in ~/.pyrax.cfg, + see U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#pyrax-configuration) + identity_type: + description: + - Authentication machanism to use, such as rackspace or keystone + default: rackspace + region: + description: + - Region to create an instance in + default: DFW + tenant_id: + description: + - The tenant ID used for authentication + tenant_name: description: - - The mount point to attach the volume to + - The tenant name used for authentication + username: + description: + - Rackspace username (overrides I(credentials)) + verify_ssl: + description: + - Whether or not to require SSL validation of API endpoints + device: + description: + - The device path to attach the volume to, e.g. /dev/xvde default: null required: true - name: + volume: description: - Name or id of the volume to attach/detach default: null required: true - region: - description: - - Region the volume and server are located in - default: DFW server: description: - Name or id of the server to attach/detach @@ -53,22 +80,24 @@ options: state: description: - Indicate desired state of the resource - choices: ['present', 'absent'] + choices: + - present + - absent default: present required: true - username: - description: - - Rackspace username (overrides C(credentials)) wait: description: - wait for the volume to be in 'in-use'/'available' state before returning default: "no" - choices: [ "yes", "no" ] + choices: + - "yes" + - "no" wait_timeout: description: - how long before wait gives up, in seconds default: 300 -requirements: [ "pyrax" ] +requirements: + - pyrax author: Christopher H. Laco, Matt Martz notes: - The following environment variables can be used, C(RAX_USERNAME), @@ -89,9 +118,9 @@ EXAMPLES = ''' local_action: module: rax_cbs_attachments credentials: ~/.raxpub - name: my-volume + volume: my-volume server: my-server - mountpoint: /dev/xvdd + device: /dev/xvdd region: DFW wait: yes state: present @@ -100,62 +129,78 @@ EXAMPLES = ''' import sys +from uuid import UUID from types import NoneType try: import pyrax + HAS_PYRAX = True except ImportError: - print("failed=True msg='pyrax required for this module'") - sys.exit(1) + HAS_PYRAX = False NON_CALLABLES = (basestring, bool, dict, int, list, NoneType) -VOLUME_STATUS = ('available', 'attaching', 'creating', 'deleting', 'in-use', - 'error', 'error_deleting') -def cloud_block_storage_attachments(module, state, name, server, mountpoint, +def cloud_block_storage_attachments(module, state, volume, server, device, wait, wait_timeout): - for arg in (state, name, server, mountpoint): + for arg in (state, volume, server, device): if not arg: - module.fail_json(msg='%s is required for rax_clb_attachments' % arg) + module.fail_json(msg='%s is required for rax_cbs_attachments' % + arg) cbs = pyrax.cloud_blockstorage cs = pyrax.cloudservers + + if cbs is None or cs is None: + module.fail_json(msg='Failed to instantiate client. This ' + 'typically indicates an invalid region or an ' + 'incorrectly capitalized region name.') + changed = False - volumes = [] instance = {} - for volume in cbs.list(): - if name == volume.display_name or name == volume.id: - volumes.append(volume) + try: + UUID(volume) + volume = cbs.get(volume) + except ValueError: + try: + volume = cbs.find(name=volume) + except Exception, e: + module.fail_json(msg='%s' % e) - if len(volumes) > 1: - module.fail_json(msg='Multiple Storage Volumes were matched by name, ' - 'try using the Volume ID instead') - elif not volumes: - module.fail_json(msg='No Storage Volumes were matched by name, ' - 'try using the Volume ID instead') + if not volume: + module.fail_json(msg='No matching storage volumes were found') - volume = volumes[0] if state == 'present': - server = cs.servers.get(server) - - if not server: - module.fail_json(msg='No Server was matched by name, ' - 'try using the Server ID instead') + try: + UUID(server) + server = cs.servers.get(server) + except ValueError: + servers = cs.servers.list(search_opts=dict(name='^%s$' % server)) + if not servers: + module.fail_json(msg='No Server was matched by name, ' + 'try using the Server ID instead') + if len(servers) > 1: + module.fail_json(msg='Multiple servers matched by name, ' + 'try using the Server ID instead') + + # We made it this far, grab the first and hopefully only server + # in the list + server = servers[0] + + if (volume.attachments and + volume.attachments[0]['server_id'] == server.id): + changed = False + elif volume.attachments: + module.fail_json(msg='Volume is attached to another server') else: - if volume.attachments and volume.attachments[0]['server_id'] == server.id: - changed = False - elif volume.attachments: - module.fail_json(msg='Volume is attached to another server') - else: - try: - volume.attach_to_instance(server, mountpoint=mountpoint) - changed = True - except Exception, e: - module.fail_json(msg='%s' % e.message) - - volume.get() + try: + volume.attach_to_instance(server, mountpoint=device) + changed = True + except Exception, e: + module.fail_json(msg='%s' % e.message) + + volume.get() for key, value in vars(volume).iteritems(): if (isinstance(value, NON_CALLABLES) and @@ -167,9 +212,9 @@ def cloud_block_storage_attachments(module, state, name, server, mountpoint, if volume.status == 'error': result['msg'] = '%s failed to build' % volume.id elif wait: + attempts = wait_timeout / 5 pyrax.utils.wait_until(volume, 'status', 'in-use', - interval=3, attempts=0, - verbose=False) + interval=5, attempts=attempts) if 'msg' in result: module.fail_json(**result) @@ -177,27 +222,38 @@ def cloud_block_storage_attachments(module, state, name, server, mountpoint, module.exit_json(**result) elif state == 'absent': - server = cs.servers.get(server) - - if not server: - module.fail_json(msg='No Server was matched by name, ' - 'try using the Server ID instead') - else: - if volume.attachments and volume.attachments[0]['server_id'] == server.id: - try: - volume.detach() - if wait: - pyrax.utils.wait_until(volume, 'status', 'available', - interval=3, attempts=0, - verbose=False) - changed = True - except Exception, e: - module.fail_json(msg='%s' % e.message) - - volume.get() + try: + UUID(server) + server = cs.servers.get(server) + except ValueError: + servers = cs.servers.list(search_opts=dict(name='^%s$' % server)) + if not servers: + module.fail_json(msg='No Server was matched by name, ' + 'try using the Server ID instead') + if len(servers) > 1: + module.fail_json(msg='Multiple servers matched by name, ' + 'try using the Server ID instead') + + # We made it this far, grab the first and hopefully only server + # in the list + server = servers[0] + + if (volume.attachments and + volume.attachments[0]['server_id'] == server.id): + try: + volume.detach() + if wait: + pyrax.utils.wait_until(volume, 'status', 'available', + interval=3, attempts=0, + verbose=False) changed = True - elif volume.attachments: - module.fail_json(msg='Volume is attached to another server') + except Exception, e: + module.fail_json(msg='%s' % e.message) + + volume.get() + changed = True + elif volume.attachments: + module.fail_json(msg='Volume is attached to another server') for key, value in vars(volume).iteritems(): if (isinstance(value, NON_CALLABLES) and @@ -221,11 +277,11 @@ def main(): argument_spec = rax_argument_spec() argument_spec.update( dict( - mountpoint=dict(), - name=dict(), - server=dict(), + device=dict(required=True), + volume=dict(required=True), + server=dict(required=True), state=dict(default='present', choices=['present', 'absent']), - wait=dict(type='bool'), + wait=dict(type='bool', default=False), wait_timeout=dict(type='int', default=300) ) ) @@ -235,16 +291,19 @@ def main(): required_together=rax_required_together() ) - mountpoint = module.params.get('mountpoint') - name = module.params.get('name') + if not HAS_PYRAX: + module.fail_json(msg='pyrax is required for this module') + + device = module.params.get('device') + volume = module.params.get('volume') server = module.params.get('server') state = module.params.get('state') wait = module.params.get('wait') - wait_timeout = int(module.params.get('wait_timeout')) + wait_timeout = module.params.get('wait_timeout') setup_rax_module(module, pyrax) - cloud_block_storage_attachments(module, state, name, server, mountpoint, + cloud_block_storage_attachments(module, state, volume, server, device, wait, wait_timeout) # import module snippets