Adds support for creating GCE persistent disks from snapshots

This commit is contained in:
Chris Conway 2014-04-11 15:45:56 -07:00
parent 0b45b1256d
commit d227330a55
6 changed files with 183 additions and 7 deletions

View file

@ -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:

View file

@ -39,6 +39,9 @@ cloud_cleanup: amazon_cleanup rackspace_cleanup
amazon_cleanup:
python cleanup_ec2.py -y --match="^$(CLOUD_RESOURCE_PREFIX)"
gce_setup:
python setup_gce.py "$(CLOUD_RESOURCE_PREFIX)"
gce_cleanup:
python cleanup_gce.py -y --match="^$(CLOUD_RESOURCE_PREFIX)"
@ -57,7 +60,8 @@ amazon: $(CREDENTIALS_FILE)
exit $$RC;
gce: $(CREDENTIALS_FILE)
ansible-playbook gce.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -e "resource_prefix=$(CLOUD_RESOURCE_PREFIX)" -v $(TEST_FLAGS) ; \
CLOUD_RESOURCE_PREFIX="$(CLOUD_RESOURCE_PREFIX)" make gce_setup ; \
ansible-playbook gce.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -e "resource_prefix=$(CLOUD_RESOURCE_PREFIX)" -v $(TEST_FLAGS) ; \
RC=$$? ; \
CLOUD_RESOURCE_PREFIX="$(CLOUD_RESOURCE_PREFIX)" make gce_cleanup ; \
exit $$RC;

View file

@ -98,6 +98,12 @@ if __name__ == '__main__':
try:
# Delete matching instances
delete_gce_resources(gce.list_nodes, 'name', opts)
# Delete matching snapshots
def get_snapshots():
for volume in gce.list_volumes():
for snapshot in gce.list_volume_snapshots(volume):
yield snapshot
delete_gce_resources(get_snapshots, 'name', opts)
# Delete matching disks
delete_gce_resources(gce.list_volumes, 'name', opts)
except KeyboardInterrupt, e:

View file

@ -0,0 +1,51 @@
import collections
import os
import yaml
try:
from libcloud.compute.types import Provider
from libcloud.compute.providers import get_driver
_ = Provider.GCE
except ImportError:
print("failed=True " + \
"msg='libcloud with GCE support (0.13.3+) required for this module'")
sys.exit(1)
def add_credentials_options(parser):
default_service_account_email=None
default_pem_file=None
default_project_id=None
# Load details from credentials.yml
if os.path.isfile('credentials.yml'):
credentials = yaml.load(open('credentials.yml', 'r'))
default_service_account_email = credentials['gce_service_account_email']
default_pem_file = credentials['gce_pem_file']
default_project_id = credentials['gce_project_id']
parser.add_option("--service_account_email",
action="store", dest="service_account_email",
default=default_service_account_email,
help="GCE service account email. Default is loaded from credentials.yml.")
parser.add_option("--pem_file",
action="store", dest="pem_file",
default=default_pem_file,
help="GCE client key. Default is loaded from credentials.yml.")
parser.add_option("--project_id",
action="store", dest="project_id",
default=default_project_id,
help="Google Cloud project ID. Default is loaded from credentials.yml.")
def check_required(opts, parser):
for required in ['service_account_email', 'pem_file', 'project_id']:
if getattr(opts, required) is None:
parser.error("Missing required parameter: --%s" % required)
def get_gce_driver(opts):
# Connect to GCE
gce_cls = get_driver(Provider.GCE)
return gce_cls(
opts.service_account_email, opts.pem_file, project=opts.project_id)

View file

@ -161,3 +161,60 @@
- 'result.changed'
- 'result.name == "{{ instance_name }}"'
- 'result.state == "absent"'
# ============================================================
- name: test snapshot given (state=present)
gce_pd:
name: "{{ instance_name }}"
snapshot: "{{ instance_name }}-snapshot"
service_account_email: "{{ service_account_email }}"
pem_file: "{{ pem_file }}"
project_id: "{{ project_id }}"
state: present
register: result
- name: assert image given (state=present)
assert:
that:
- 'result.changed'
- 'result.name == "{{ instance_name }}"'
- 'result.snapshot == "{{ instance_name }}-snapshot"'
- 'result.state == "present"'
# ============================================================
- name: test snapshot given (state=absent)
gce_pd:
name: "{{ instance_name }}"
snapshot: "{{ instance_name }}-snapshot"
service_account_email: "{{ service_account_email }}"
pem_file: "{{ pem_file }}"
project_id: "{{ project_id }}"
state: absent
register: result
- name: assert image given (state=absent)
assert:
that:
- 'result.changed'
- 'result.name == "{{ instance_name }}"'
- 'result.state == "absent"'
# ============================================================
- name: test both image and snapshot given
gce_pd:
name: "{{ instance_name }}"
image: "debian-7"
snapshot: "{{ instance_name }}-snapshot"
service_account_email: "{{ service_account_email }}"
pem_file: "{{ pem_file }}"
project_id: "{{ project_id }}"
state: present
register: result
ignore_errors: true
- name: assert image given (state=present)
assert:
that:
- 'result.failed'
- 'result.msg == "Cannot give both image (debian-7) and snapshot ({{ instance_name }}-snapshot)"'

View file

@ -0,0 +1,40 @@
'''
Create GCE resources for use in integration tests.
Takes a prefix as a command-line argument and creates a persistent disk
named ${prefix}-base and a snapshot of it named ${prefix}-snapshot.
prefix will be forced to lowercase, to ensure the names are legal GCE
resource names.
'''
import sys
import optparse
import gce_credentials
def parse_args():
parser = optparse.OptionParser(
usage="%s [options] <prefix>" % (sys.argv[0],), description=__doc__)
gce_credentials.add_credentials_options(parser)
parser.add_option("--prefix",
action="store", dest="prefix",
help="String used to prefix GCE resource names (default: %default)")
(opts, args) = parser.parse_args()
gce_credentials.check_required(opts, parser)
if not args:
parser.error("Missing required argument: name prefix")
return (opts, args)
if __name__ == '__main__':
(opts, args) = parse_args()
gce = gce_credentials.get_gce_driver(opts)
prefix = args[0].lower()
try:
base_volume = gce.create_volume(
size=10, name=prefix+'-base', location='us-central1-a')
gce.create_volume_snapshot(base_volume, name=prefix+'-snapshot')
except KeyboardInterrupt, e:
print "\nExiting on user command."