Add boot from volume functionality to rax and rax_cbs modules
This commit is contained in:
parent
98d86529f4
commit
b0dcff214a
2 changed files with 184 additions and 23 deletions
|
@ -35,6 +35,34 @@ options:
|
|||
- "yes"
|
||||
- "no"
|
||||
version_added: 1.5
|
||||
boot_from_volume:
|
||||
description:
|
||||
- Whether or not to boot the instance from a Cloud Block Storage volume.
|
||||
If C(yes) and I(image) is specified a new volume will be created at
|
||||
boot time. I(boot_volume_size) is required with I(image) to create a
|
||||
new volume at boot time.
|
||||
default: "no"
|
||||
choices:
|
||||
- "yes"
|
||||
- "no"
|
||||
version_added: 1.9
|
||||
boot_volume:
|
||||
description:
|
||||
- Cloud Block Storage ID or Name to use as the boot volume of the
|
||||
instance
|
||||
version_added: 1.9
|
||||
boot_volume_size:
|
||||
description:
|
||||
- Size of the volume to create in Gigabytes. This is only required with
|
||||
I(image) and I(boot_from_volume).
|
||||
default: 100
|
||||
version_added: 1.9
|
||||
boot_volume_terminate:
|
||||
description:
|
||||
- Whether the I(boot_volume) or newly created volume from I(image) will
|
||||
be terminated when the server is terminated
|
||||
default: false
|
||||
version_added: 1.9
|
||||
config_drive:
|
||||
description:
|
||||
- Attach read-only configuration drive to server as label config-2
|
||||
|
@ -99,7 +127,9 @@ options:
|
|||
version_added: 1.4
|
||||
image:
|
||||
description:
|
||||
- image to use for the instance. Can be an C(id), C(human_id) or C(name)
|
||||
- image to use for the instance. Can be an C(id), C(human_id) or C(name).
|
||||
With I(boot_from_volume), a Cloud Block Storage volume will be created
|
||||
with this image
|
||||
default: null
|
||||
instance_ids:
|
||||
description:
|
||||
|
@ -213,7 +243,7 @@ except ImportError:
|
|||
def create(module, names=[], flavor=None, image=None, meta={}, key_name=None,
|
||||
files={}, wait=True, wait_timeout=300, disk_config=None,
|
||||
group=None, nics=[], extra_create_args={}, user_data=None,
|
||||
config_drive=False, existing=[]):
|
||||
config_drive=False, existing=[], block_device_mapping_v2=[]):
|
||||
cs = pyrax.cloudservers
|
||||
changed = False
|
||||
|
||||
|
@ -239,6 +269,7 @@ def create(module, names=[], flavor=None, image=None, meta={}, key_name=None,
|
|||
module.fail_json(msg='Failed to load %s' % lpath)
|
||||
try:
|
||||
servers = []
|
||||
bdmv2 = block_device_mapping_v2
|
||||
for name in names:
|
||||
servers.append(cs.servers.create(name=name, image=image,
|
||||
flavor=flavor, meta=meta,
|
||||
|
@ -247,6 +278,7 @@ def create(module, names=[], flavor=None, image=None, meta={}, key_name=None,
|
|||
disk_config=disk_config,
|
||||
config_drive=config_drive,
|
||||
userdata=user_data,
|
||||
block_device_mapping_v2=bdmv2,
|
||||
**extra_create_args))
|
||||
except Exception, e:
|
||||
module.fail_json(msg='%s' % e.message)
|
||||
|
@ -394,7 +426,9 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
|
|||
disk_config=None, count=1, group=None, instance_ids=[],
|
||||
exact_count=False, networks=[], count_offset=0,
|
||||
auto_increment=False, extra_create_args={}, user_data=None,
|
||||
config_drive=False):
|
||||
config_drive=False, boot_from_volume=False,
|
||||
boot_volume=None, boot_volume_size=None,
|
||||
boot_volume_terminate=False):
|
||||
cs = pyrax.cloudservers
|
||||
cnw = pyrax.cloud_networks
|
||||
if not cnw:
|
||||
|
@ -402,6 +436,26 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
|
|||
'typically indicates an invalid region or an '
|
||||
'incorrectly capitalized region name.')
|
||||
|
||||
if state == 'present' or (state == 'absent' and instance_ids is None):
|
||||
for arg, value in dict(name=name, flavor=flavor).iteritems():
|
||||
if not value:
|
||||
module.fail_json(msg='%s is required for the "rax" module' %
|
||||
arg)
|
||||
|
||||
if not boot_from_volume and not boot_volume and not image:
|
||||
module.fail_json(msg='image is required for the "rax" module')
|
||||
|
||||
if boot_from_volume and not image and not boot_volume:
|
||||
module.fail_json(msg='image or boot_volume are required for the '
|
||||
'"rax" with boot_from_volume')
|
||||
|
||||
if boot_from_volume and image and not boot_volume_size:
|
||||
module.fail_json(msg='boot_volume_size is required for the "rax" '
|
||||
'module with boot_from_volume and image')
|
||||
|
||||
if boot_from_volume and image and boot_volume:
|
||||
image = None
|
||||
|
||||
servers = []
|
||||
|
||||
# Add the group meta key
|
||||
|
@ -438,12 +492,6 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
|
|||
|
||||
# act on the state
|
||||
if state == 'present':
|
||||
for arg, value in dict(name=name, flavor=flavor,
|
||||
image=image).iteritems():
|
||||
if not value:
|
||||
module.fail_json(msg='%s is required for the "rax" module' %
|
||||
arg)
|
||||
|
||||
# Idempotent ensurance of a specific count of servers
|
||||
if exact_count is not False:
|
||||
# See if we can find servers that match our options
|
||||
|
@ -583,7 +631,6 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
|
|||
# Perform more simplistic matching
|
||||
search_opts = {
|
||||
'name': '^%s$' % name,
|
||||
'image': image,
|
||||
'flavor': flavor
|
||||
}
|
||||
servers = []
|
||||
|
@ -591,6 +638,36 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
|
|||
# Ignore DELETED servers
|
||||
if server.status == 'DELETED':
|
||||
continue
|
||||
|
||||
if not image and boot_volume:
|
||||
vol = rax_find_bootable_volume(module, pyrax, server,
|
||||
exit=False)
|
||||
if not vol:
|
||||
continue
|
||||
volume_image_metadata = vol.volume_image_metadata
|
||||
vol_image_id = volume_image_metadata.get('image_id')
|
||||
if vol_image_id:
|
||||
server_image = rax_find_image(module, pyrax,
|
||||
vol_image_id, exit=False)
|
||||
if server_image:
|
||||
server.image = dict(id=server_image)
|
||||
|
||||
# Match image IDs taking care of boot from volume
|
||||
if image and not server.image:
|
||||
vol = rax_find_bootable_volume(module, pyrax, server)
|
||||
volume_image_metadata = vol.volume_image_metadata
|
||||
vol_image_id = volume_image_metadata.get('image_id')
|
||||
if not vol_image_id:
|
||||
continue
|
||||
server_image = rax_find_image(module, pyrax,
|
||||
vol_image_id, exit=False)
|
||||
if image != server_image:
|
||||
continue
|
||||
|
||||
server.image = dict(id=server_image)
|
||||
elif image and server.image['id'] != image:
|
||||
continue
|
||||
|
||||
# Ignore servers with non matching metadata
|
||||
if server.metadata != meta:
|
||||
continue
|
||||
|
@ -616,34 +693,85 @@ def cloudservers(module, state=None, name=None, flavor=None, image=None,
|
|||
# them, we aren't performing auto_increment here
|
||||
names = [name] * (count - len(servers))
|
||||
|
||||
block_device_mapping_v2 = []
|
||||
if boot_from_volume:
|
||||
mapping = {
|
||||
'boot_index': '0',
|
||||
'delete_on_termination': boot_volume_terminate,
|
||||
'destination_type': 'volume',
|
||||
}
|
||||
if image:
|
||||
if boot_volume_size < 100:
|
||||
module.fail_json(msg='"boot_volume_size" must be greater '
|
||||
'than or equal to 100')
|
||||
mapping.update({
|
||||
'uuid': image,
|
||||
'source_type': 'image',
|
||||
'volume_size': boot_volume_size,
|
||||
})
|
||||
image = None
|
||||
elif boot_volume:
|
||||
volume = rax_find_volume(module, pyrax, boot_volume)
|
||||
mapping.update({
|
||||
'uuid': pyrax.utils.get_id(volume),
|
||||
'source_type': 'volume',
|
||||
})
|
||||
block_device_mapping_v2.append(mapping)
|
||||
|
||||
create(module, names=names, flavor=flavor, image=image,
|
||||
meta=meta, key_name=key_name, files=files, wait=wait,
|
||||
wait_timeout=wait_timeout, disk_config=disk_config, group=group,
|
||||
nics=nics, extra_create_args=extra_create_args,
|
||||
user_data=user_data, config_drive=config_drive,
|
||||
existing=servers)
|
||||
existing=servers,
|
||||
block_device_mapping_v2=block_device_mapping_v2)
|
||||
|
||||
elif state == 'absent':
|
||||
if instance_ids is None:
|
||||
# We weren't given an explicit list of server IDs to delete
|
||||
# Let's match instead
|
||||
for arg, value in dict(name=name, flavor=flavor,
|
||||
image=image).iteritems():
|
||||
if not value:
|
||||
module.fail_json(msg='%s is required for the "rax" '
|
||||
'module' % arg)
|
||||
search_opts = {
|
||||
'name': '^%s$' % name,
|
||||
'image': image,
|
||||
'flavor': flavor
|
||||
}
|
||||
for server in cs.servers.list(search_opts=search_opts):
|
||||
# Ignore DELETED servers
|
||||
if server.status == 'DELETED':
|
||||
continue
|
||||
|
||||
if not image and boot_volume:
|
||||
vol = rax_find_bootable_volume(module, pyrax, server,
|
||||
exit=False)
|
||||
if not vol:
|
||||
continue
|
||||
volume_image_metadata = vol.volume_image_metadata
|
||||
vol_image_id = volume_image_metadata.get('image_id')
|
||||
if vol_image_id:
|
||||
server_image = rax_find_image(module, pyrax,
|
||||
vol_image_id, exit=False)
|
||||
if server_image:
|
||||
server.image = dict(id=server_image)
|
||||
|
||||
# Match image IDs taking care of boot from volume
|
||||
if image and not server.image:
|
||||
vol = rax_find_bootable_volume(module, pyrax, server)
|
||||
volume_image_metadata = vol.volume_image_metadata
|
||||
vol_image_id = volume_image_metadata.get('image_id')
|
||||
if not vol_image_id:
|
||||
continue
|
||||
server_image = rax_find_image(module, pyrax,
|
||||
vol_image_id, exit=False)
|
||||
if image != server_image:
|
||||
continue
|
||||
|
||||
server.image = dict(id=server_image)
|
||||
elif image and server.image['id'] != image:
|
||||
continue
|
||||
|
||||
# Ignore servers with non matching metadata
|
||||
if meta != server.metadata:
|
||||
continue
|
||||
|
||||
servers.append(server)
|
||||
|
||||
# Build a list of server IDs to delete
|
||||
|
@ -672,6 +800,10 @@ def main():
|
|||
argument_spec.update(
|
||||
dict(
|
||||
auto_increment=dict(default=True, type='bool'),
|
||||
boot_from_volume=dict(default=False, type='bool'),
|
||||
boot_volume=dict(type='str'),
|
||||
boot_volume_size=dict(type='int', default=100),
|
||||
boot_volume_terminate=dict(type='bool', default=False),
|
||||
config_drive=dict(default=False, type='bool'),
|
||||
count=dict(default=1, type='int'),
|
||||
count_offset=dict(default=1, type='int'),
|
||||
|
@ -712,6 +844,10 @@ def main():
|
|||
'playbook pertaining to the "rax" module')
|
||||
|
||||
auto_increment = module.params.get('auto_increment')
|
||||
boot_from_volume = module.params.get('boot_from_volume')
|
||||
boot_volume = module.params.get('boot_volume')
|
||||
boot_volume_size = module.params.get('boot_volume_size')
|
||||
boot_volume_terminate = module.params.get('boot_volume_terminate')
|
||||
config_drive = module.params.get('config_drive')
|
||||
count = module.params.get('count')
|
||||
count_offset = module.params.get('count_offset')
|
||||
|
@ -757,7 +893,9 @@ def main():
|
|||
exact_count=exact_count, networks=networks,
|
||||
count_offset=count_offset, auto_increment=auto_increment,
|
||||
extra_create_args=extra_create_args, user_data=user_data,
|
||||
config_drive=config_drive)
|
||||
config_drive=config_drive, boot_from_volume=boot_from_volume,
|
||||
boot_volume=boot_volume, boot_volume_size=boot_volume_size,
|
||||
boot_volume_terminate=boot_volume_terminate)
|
||||
|
||||
|
||||
# import module snippets
|
||||
|
|
|
@ -28,6 +28,12 @@ options:
|
|||
description:
|
||||
- Description to give the volume being created
|
||||
default: null
|
||||
image:
|
||||
description:
|
||||
- image to use for bootable volumes. Can be an C(id), C(human_id) or
|
||||
C(name). This option requires C(pyrax>=1.9.3)
|
||||
default: null
|
||||
version_added: 1.9
|
||||
meta:
|
||||
description:
|
||||
- A hash of metadata to associate with the volume
|
||||
|
@ -99,6 +105,8 @@ EXAMPLES = '''
|
|||
register: my_volume
|
||||
'''
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
try:
|
||||
import pyrax
|
||||
HAS_PYRAX = True
|
||||
|
@ -107,7 +115,8 @@ except ImportError:
|
|||
|
||||
|
||||
def cloud_block_storage(module, state, name, description, meta, size,
|
||||
snapshot_id, volume_type, wait, wait_timeout):
|
||||
snapshot_id, volume_type, wait, wait_timeout,
|
||||
image):
|
||||
changed = False
|
||||
volume = None
|
||||
instance = {}
|
||||
|
@ -119,15 +128,26 @@ def cloud_block_storage(module, state, name, description, meta, size,
|
|||
'typically indicates an invalid region or an '
|
||||
'incorrectly capitalized region name.')
|
||||
|
||||
if image:
|
||||
# pyrax<1.9.3 did not have support for specifying an image when
|
||||
# creating a volume which is required for bootable volumes
|
||||
if LooseVersion(pyrax.version.version) < LooseVersion('1.9.3'):
|
||||
module.fail_json(msg='Creating a bootable volume requires '
|
||||
'pyrax>=1.9.3')
|
||||
image = rax_find_image(module, pyrax, image)
|
||||
|
||||
volume = rax_find_volume(module, pyrax, name)
|
||||
|
||||
if state == 'present':
|
||||
if not volume:
|
||||
kwargs = dict()
|
||||
if image:
|
||||
kwargs['image'] = image
|
||||
try:
|
||||
volume = cbs.create(name, size=size, volume_type=volume_type,
|
||||
description=description,
|
||||
metadata=meta,
|
||||
snapshot_id=snapshot_id)
|
||||
snapshot_id=snapshot_id, **kwargs)
|
||||
changed = True
|
||||
except Exception, e:
|
||||
module.fail_json(msg='%s' % e.message)
|
||||
|
@ -168,7 +188,8 @@ def main():
|
|||
argument_spec = rax_argument_spec()
|
||||
argument_spec.update(
|
||||
dict(
|
||||
description=dict(),
|
||||
description=dict(type='str'),
|
||||
image=dict(type='str'),
|
||||
meta=dict(type='dict', default={}),
|
||||
name=dict(required=True),
|
||||
size=dict(type='int', default=100),
|
||||
|
@ -189,6 +210,7 @@ def main():
|
|||
module.fail_json(msg='pyrax is required for this module')
|
||||
|
||||
description = module.params.get('description')
|
||||
image = module.params.get('image')
|
||||
meta = module.params.get('meta')
|
||||
name = module.params.get('name')
|
||||
size = module.params.get('size')
|
||||
|
@ -201,11 +223,12 @@ def main():
|
|||
setup_rax_module(module, pyrax)
|
||||
|
||||
cloud_block_storage(module, state, name, description, meta, size,
|
||||
snapshot_id, volume_type, wait, wait_timeout)
|
||||
snapshot_id, volume_type, wait, wait_timeout,
|
||||
image)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.rax import *
|
||||
|
||||
### invoke the module
|
||||
# invoke the module
|
||||
main()
|
||||
|
|
Loading…
Reference in a new issue