From c631178db0ea6f604c5da6ba34ae1fa06334bd0f Mon Sep 17 00:00:00 2001 From: Chris Conway Date: Fri, 11 Apr 2014 14:40:52 -0700 Subject: [PATCH 1/4] Adds support for creating GCE persistent disks from images --- cloud/gce_pd | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cloud/gce_pd b/cloud/gce_pd index e5ea6cc4ad8..98e8e8d15e8 100644 --- a/cloud/gce_pd +++ b/cloud/gce_pd @@ -62,6 +62,12 @@ options: required: false default: 10 aliases: [] + image: + description: + - the source image to use for the disk + required: false + default: null + aliases: [] state: description: - desired state of the persistent disk @@ -132,6 +138,7 @@ def main(): mode = dict(default='READ_ONLY', choices=['READ_WRITE', 'READ_ONLY']), name = dict(required=True), size_gb = dict(default=10), + image = dict(), state = dict(default='present'), zone = dict(default='us-central1-b'), service_account_email = dict(), @@ -147,6 +154,7 @@ def main(): mode = module.params.get('mode') name = module.params.get('name') size_gb = module.params.get('size_gb') + image = module.params.get('image') state = module.params.get('state') zone = module.params.get('zone') @@ -204,8 +212,11 @@ def main(): instance_name, zone), changed=False) if not disk: + lc_image = None + if image is not None: + lc_image = gce.ex_get_image(image) try: - disk = gce.create_volume(size_gb, name, location=zone) + disk = gce.create_volume(size_gb, name, location=zone, image=lc_image) except ResourceExistsError: pass except QuotaExceededError: @@ -214,6 +225,7 @@ def main(): except Exception, e: module.fail_json(msg=unexpected_error_msg(e), changed=False) json_output['size_gb'] = size_gb + json_output['image'] = image changed = True if inst and not is_attached: try: From 03250b0ace90e04b554c7b971ba3bb427cae6c19 Mon Sep 17 00:00:00 2001 From: Chris Conway Date: Fri, 11 Apr 2014 15:45:56 -0700 Subject: [PATCH 2/4] Adds support for creating GCE persistent disks from snapshots --- cloud/gce_pd | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/cloud/gce_pd b/cloud/gce_pd index 98e8e8d15e8..0032acd57e4 100644 --- a/cloud/gce_pd +++ b/cloud/gce_pd @@ -24,9 +24,7 @@ short_description: utilize GCE persistent disk resources description: - This module can create and destroy unformatted GCE persistent disks U(https://developers.google.com/compute/docs/disks#persistentdisks). - It also supports attaching and detaching disks from running instances - but does not support creating boot disks from images or snapshots. The - 'gce' module supports creating instances with boot disks. + It also supports attaching and detaching disks from running instances. Full install/configuration instructions for the gce* modules can be found in the comments of ansible/test/gce_tests.py. options: @@ -68,6 +66,12 @@ options: required: false default: null aliases: [] + snapshot: + description: + - the source snapshot to use for the disk + required: false + default: null + aliases: [] state: description: - desired state of the persistent disk @@ -139,6 +143,7 @@ def main(): name = dict(required=True), size_gb = dict(default=10), image = dict(), + snapshot = dict(), state = dict(default='present'), zone = dict(default='us-central1-b'), service_account_email = dict(), @@ -155,6 +160,7 @@ def main(): name = module.params.get('name') size_gb = module.params.get('size_gb') image = module.params.get('image') + snapshot = module.params.get('snapshot') state = module.params.get('state') zone = module.params.get('zone') @@ -212,11 +218,20 @@ def main(): instance_name, zone), changed=False) if not disk: + if image is not None and snapshot is not None: + module.fail_json( + msg='Cannot give both image (%s) and snapshot (%s)' % ( + image, snapshot), changed=False) lc_image = None + lc_snapshot = None if image is not None: - lc_image = gce.ex_get_image(image) + lc_image = gce.ex_get_image(image) + elif snapshot is not None: + lc_snapshot = gce.ex_get_snapshot(snapshot) try: - disk = gce.create_volume(size_gb, name, location=zone, image=lc_image) + disk = gce.create_volume( + size_gb, name, location=zone, image=lc_image, + snapshot=lc_snapshot) except ResourceExistsError: pass except QuotaExceededError: @@ -225,7 +240,10 @@ def main(): except Exception, e: module.fail_json(msg=unexpected_error_msg(e), changed=False) json_output['size_gb'] = size_gb - json_output['image'] = image + if image is not None: + json_output['image'] = image + if snapshot is not None: + json_output['snapshot'] = snapshot changed = True if inst and not is_attached: try: From dc8a7775f00f2e658ba6158b93089474b8814ce5 Mon Sep 17 00:00:00 2001 From: Chris Conway Date: Sat, 12 Apr 2014 15:21:26 -0700 Subject: [PATCH 3/4] Adds support for attaching persistent disks to GCE instances --- cloud/gce | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/cloud/gce b/cloud/gce index 2d95c8143bc..3484990526b 100755 --- a/cloud/gce +++ b/cloud/gce @@ -89,6 +89,12 @@ options: required: false default: "false" aliases: [] + disks: + description: + - a list of persistent disks to attach to the instance; a string value gives the name of the disk; alternatively, a dictionary value can define 'name' and 'mode' ('READ_ONLY' or 'READ_WRITE'). The first entry will be the boot disk (which must be READ_WRITE). + required: false + default: null + aliases: [] state: description: - desired state of the resource @@ -210,8 +216,16 @@ def get_instance_info(inst): netname = inst.extra['networkInterfaces'][0]['network'].split('/')[-1] except: netname = None + if 'disks' in inst.extra: + disk_names = [disk_info['source'].split('/')[-1] + for disk_info + in sorted(inst.extra['disks'], + key=lambda disk_info: disk_info['index'])] + else: + disk_names = [] return({ 'image': not inst.image is None and inst.image.split('/')[-1] or None, + 'disks': disk_names, 'machine_type': inst.size, 'metadata': metadata, 'name': inst.name, @@ -241,6 +255,7 @@ def create_instances(module, gce, instance_names): metadata = module.params.get('metadata') network = module.params.get('network') persistent_boot_disk = module.params.get('persistent_boot_disk') + disks = module.params.get('disks') state = module.params.get('state') tags = module.params.get('tags') zone = module.params.get('zone') @@ -249,6 +264,16 @@ def create_instances(module, gce, instance_names): changed = False lc_image = gce.ex_get_image(image) + lc_disks = [] + disk_modes = [] + for i, disk in enumerate(disks or []): + if isinstance(disk, dict): + lc_disks.append(gce.ex_get_volume(disk['name'])) + disk_modes.append(disk['mode']) + else: + lc_disks.append(gce.ex_get_volume(disk)) + # boot disk is implicitly READ_WRITE + disk_modes.append('READ_ONLY' if i > 0 else 'READ_WRITE') lc_network = gce.ex_get_network(network) lc_machine_type = gce.ex_get_size(machine_type) lc_zone = gce.ex_get_zone(zone) @@ -283,7 +308,9 @@ def create_instances(module, gce, instance_names): for name in instance_names: pd = None - if persistent_boot_disk: + if lc_disks: + pd = lc_disks[0] + elif persistent_boot_disk: try: pd = gce.create_volume(None, "%s" % name, image=lc_image) except ResourceExistsError: @@ -300,6 +327,28 @@ def create_instances(module, gce, instance_names): module.fail_json(msg='Unexpected error attempting to create ' + \ 'instance %s, error: %s' % (name, e.value)) + for i, lc_disk in enumerate(lc_disks): + # Check whether the disk is already attached + if (len(inst.extra['disks']) > i): + attached_disk = inst.extra['disks'][i] + if attached_disk['source'] != lc_disk.extra['selfLink']: + module.fail_json( + msg=("Disk at index %d does not match: requested=%s found=%s" % ( + i, lc_disk.extra['selfLink'], attached_disk['source']))) + elif attached_disk['mode'] != disk_modes[i]: + module.fail_json( + msg=("Disk at index %d is in the wrong mode: requested=%s found=%s" % ( + i, disk_modes[i], attached_disk['mode']))) + else: + continue + gce.attach_volume(inst, lc_disk, ex_mode=disk_modes[i]) + # Work around libcloud bug: attached volumes don't get added + # to the instance metadata. get_instance_info() only cares about + # source and index. + if len(inst.extra['disks']) != i+1: + inst.extra['disks'].append( + {'source': lc_disk.extra['selfLink'], 'index': i}) + if inst: new_instances.append(inst) @@ -352,6 +401,7 @@ def main(): name = dict(), network = dict(default='default'), persistent_boot_disk = dict(type='bool', default=False), + disks = dict(type='list'), state = dict(choices=['active', 'present', 'absent', 'deleted'], default='present'), tags = dict(type='list'), From ce38619394f1eec90c1241777d58463867978e75 Mon Sep 17 00:00:00 2001 From: Chris Conway Date: Mon, 14 Apr 2014 08:33:45 -0700 Subject: [PATCH 4/4] Adds version_added property to new attributes in gce and gce_pd. --- cloud/gce | 1 + cloud/gce_pd | 2 ++ 2 files changed, 3 insertions(+) diff --git a/cloud/gce b/cloud/gce index 3484990526b..c2ba9228121 100755 --- a/cloud/gce +++ b/cloud/gce @@ -95,6 +95,7 @@ options: required: false default: null aliases: [] + version_added: "1.6" state: description: - desired state of the resource diff --git a/cloud/gce_pd b/cloud/gce_pd index 0032acd57e4..8dd42d04fa4 100644 --- a/cloud/gce_pd +++ b/cloud/gce_pd @@ -66,12 +66,14 @@ options: required: false default: null aliases: [] + version_added: "1.6" snapshot: description: - the source snapshot to use for the disk required: false default: null aliases: [] + version_added: "1.6" state: description: - desired state of the persistent disk