mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Merge pull request #6976 from clconway/gce-snapshots
Adds support for snapshots and extra persistent disks to the gce modules
This commit is contained in:
commit
98c6688343
12 changed files with 725 additions and 5 deletions
|
@ -89,6 +89,13 @@ options:
|
||||||
required: false
|
required: false
|
||||||
default: "false"
|
default: "false"
|
||||||
aliases: []
|
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: []
|
||||||
|
version_added: "1.6"
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- desired state of the resource
|
- desired state of the resource
|
||||||
|
@ -209,8 +216,16 @@ def get_instance_info(inst):
|
||||||
netname = inst.extra['networkInterfaces'][0]['network'].split('/')[-1]
|
netname = inst.extra['networkInterfaces'][0]['network'].split('/')[-1]
|
||||||
except:
|
except:
|
||||||
netname = None
|
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({
|
return({
|
||||||
'image': not inst.image is None and inst.image.split('/')[-1] or None,
|
'image': not inst.image is None and inst.image.split('/')[-1] or None,
|
||||||
|
'disks': disk_names,
|
||||||
'machine_type': inst.size,
|
'machine_type': inst.size,
|
||||||
'metadata': metadata,
|
'metadata': metadata,
|
||||||
'name': inst.name,
|
'name': inst.name,
|
||||||
|
@ -240,6 +255,7 @@ def create_instances(module, gce, instance_names):
|
||||||
metadata = module.params.get('metadata')
|
metadata = module.params.get('metadata')
|
||||||
network = module.params.get('network')
|
network = module.params.get('network')
|
||||||
persistent_boot_disk = module.params.get('persistent_boot_disk')
|
persistent_boot_disk = module.params.get('persistent_boot_disk')
|
||||||
|
disks = module.params.get('disks')
|
||||||
state = module.params.get('state')
|
state = module.params.get('state')
|
||||||
tags = module.params.get('tags')
|
tags = module.params.get('tags')
|
||||||
zone = module.params.get('zone')
|
zone = module.params.get('zone')
|
||||||
|
@ -248,6 +264,16 @@ def create_instances(module, gce, instance_names):
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
lc_image = gce.ex_get_image(image)
|
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_network = gce.ex_get_network(network)
|
||||||
lc_machine_type = gce.ex_get_size(machine_type)
|
lc_machine_type = gce.ex_get_size(machine_type)
|
||||||
lc_zone = gce.ex_get_zone(zone)
|
lc_zone = gce.ex_get_zone(zone)
|
||||||
|
@ -282,7 +308,9 @@ def create_instances(module, gce, instance_names):
|
||||||
|
|
||||||
for name in instance_names:
|
for name in instance_names:
|
||||||
pd = None
|
pd = None
|
||||||
if persistent_boot_disk:
|
if lc_disks:
|
||||||
|
pd = lc_disks[0]
|
||||||
|
elif persistent_boot_disk:
|
||||||
try:
|
try:
|
||||||
pd = gce.create_volume(None, "%s" % name, image=lc_image)
|
pd = gce.create_volume(None, "%s" % name, image=lc_image)
|
||||||
except ResourceExistsError:
|
except ResourceExistsError:
|
||||||
|
@ -299,6 +327,28 @@ def create_instances(module, gce, instance_names):
|
||||||
module.fail_json(msg='Unexpected error attempting to create ' + \
|
module.fail_json(msg='Unexpected error attempting to create ' + \
|
||||||
'instance %s, error: %s' % (name, e.value))
|
'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:
|
if inst:
|
||||||
new_instances.append(inst)
|
new_instances.append(inst)
|
||||||
|
|
||||||
|
@ -351,6 +401,7 @@ def main():
|
||||||
name = dict(),
|
name = dict(),
|
||||||
network = dict(default='default'),
|
network = dict(default='default'),
|
||||||
persistent_boot_disk = dict(type='bool', default=False),
|
persistent_boot_disk = dict(type='bool', default=False),
|
||||||
|
disks = dict(type='list'),
|
||||||
state = dict(choices=['active', 'present', 'absent', 'deleted'],
|
state = dict(choices=['active', 'present', 'absent', 'deleted'],
|
||||||
default='present'),
|
default='present'),
|
||||||
tags = dict(type='list'),
|
tags = dict(type='list'),
|
||||||
|
|
|
@ -24,9 +24,7 @@ short_description: utilize GCE persistent disk resources
|
||||||
description:
|
description:
|
||||||
- This module can create and destroy unformatted GCE persistent disks
|
- This module can create and destroy unformatted GCE persistent disks
|
||||||
U(https://developers.google.com/compute/docs/disks#persistentdisks).
|
U(https://developers.google.com/compute/docs/disks#persistentdisks).
|
||||||
It also supports attaching and detaching disks from running instances
|
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.
|
|
||||||
Full install/configuration instructions for the gce* modules can
|
Full install/configuration instructions for the gce* modules can
|
||||||
be found in the comments of ansible/test/gce_tests.py.
|
be found in the comments of ansible/test/gce_tests.py.
|
||||||
options:
|
options:
|
||||||
|
@ -62,6 +60,20 @@ options:
|
||||||
required: false
|
required: false
|
||||||
default: 10
|
default: 10
|
||||||
aliases: []
|
aliases: []
|
||||||
|
image:
|
||||||
|
description:
|
||||||
|
- the source image to use for the disk
|
||||||
|
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:
|
state:
|
||||||
description:
|
description:
|
||||||
- desired state of the persistent disk
|
- desired state of the persistent disk
|
||||||
|
@ -132,6 +144,8 @@ def main():
|
||||||
mode = dict(default='READ_ONLY', choices=['READ_WRITE', 'READ_ONLY']),
|
mode = dict(default='READ_ONLY', choices=['READ_WRITE', 'READ_ONLY']),
|
||||||
name = dict(required=True),
|
name = dict(required=True),
|
||||||
size_gb = dict(default=10),
|
size_gb = dict(default=10),
|
||||||
|
image = dict(),
|
||||||
|
snapshot = dict(),
|
||||||
state = dict(default='present'),
|
state = dict(default='present'),
|
||||||
zone = dict(default='us-central1-b'),
|
zone = dict(default='us-central1-b'),
|
||||||
service_account_email = dict(),
|
service_account_email = dict(),
|
||||||
|
@ -147,6 +161,8 @@ def main():
|
||||||
mode = module.params.get('mode')
|
mode = module.params.get('mode')
|
||||||
name = module.params.get('name')
|
name = module.params.get('name')
|
||||||
size_gb = module.params.get('size_gb')
|
size_gb = module.params.get('size_gb')
|
||||||
|
image = module.params.get('image')
|
||||||
|
snapshot = module.params.get('snapshot')
|
||||||
state = module.params.get('state')
|
state = module.params.get('state')
|
||||||
zone = module.params.get('zone')
|
zone = module.params.get('zone')
|
||||||
|
|
||||||
|
@ -204,8 +220,20 @@ def main():
|
||||||
instance_name, zone), changed=False)
|
instance_name, zone), changed=False)
|
||||||
|
|
||||||
if not disk:
|
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)
|
||||||
|
elif snapshot is not None:
|
||||||
|
lc_snapshot = gce.ex_get_snapshot(snapshot)
|
||||||
try:
|
try:
|
||||||
disk = gce.create_volume(size_gb, name, location=zone)
|
disk = gce.create_volume(
|
||||||
|
size_gb, name, location=zone, image=lc_image,
|
||||||
|
snapshot=lc_snapshot)
|
||||||
except ResourceExistsError:
|
except ResourceExistsError:
|
||||||
pass
|
pass
|
||||||
except QuotaExceededError:
|
except QuotaExceededError:
|
||||||
|
@ -214,6 +242,10 @@ def main():
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
json_output['size_gb'] = size_gb
|
json_output['size_gb'] = size_gb
|
||||||
|
if image is not None:
|
||||||
|
json_output['image'] = image
|
||||||
|
if snapshot is not None:
|
||||||
|
json_output['snapshot'] = snapshot
|
||||||
changed = True
|
changed = True
|
||||||
if inst and not is_attached:
|
if inst and not is_attached:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -56,6 +56,12 @@ cloud_cleanup: amazon_cleanup rackspace_cleanup
|
||||||
amazon_cleanup:
|
amazon_cleanup:
|
||||||
python cleanup_ec2.py -y --match="^$(CLOUD_RESOURCE_PREFIX)"
|
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)"
|
||||||
|
|
||||||
rackspace_cleanup:
|
rackspace_cleanup:
|
||||||
@echo "FIXME - cleanup_rax.py not yet implemented"
|
@echo "FIXME - cleanup_rax.py not yet implemented"
|
||||||
@# python cleanup_rax.py -y --match="^$(CLOUD_RESOURCE_PREFIX)"
|
@# python cleanup_rax.py -y --match="^$(CLOUD_RESOURCE_PREFIX)"
|
||||||
|
@ -70,6 +76,13 @@ amazon: $(CREDENTIALS_FILE)
|
||||||
CLOUD_RESOURCE_PREFIX="$(CLOUD_RESOURCE_PREFIX)" make amazon_cleanup ; \
|
CLOUD_RESOURCE_PREFIX="$(CLOUD_RESOURCE_PREFIX)" make amazon_cleanup ; \
|
||||||
exit $$RC;
|
exit $$RC;
|
||||||
|
|
||||||
|
gce: $(CREDENTIALS_FILE)
|
||||||
|
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;
|
||||||
|
|
||||||
rackspace: $(CREDENTIALS_FILE)
|
rackspace: $(CREDENTIALS_FILE)
|
||||||
ansible-playbook rackspace.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -e "resource_prefix=$(CLOUD_RESOURCE_PREFIX)" -v $(TEST_FLAGS) ; \
|
ansible-playbook rackspace.yml -i $(INVENTORY) -e @$(VARS_FILE) $(CREDENTIALS_ARG) -e "resource_prefix=$(CLOUD_RESOURCE_PREFIX)" -v $(TEST_FLAGS) ; \
|
||||||
RC=$$? ; \
|
RC=$$? ; \
|
||||||
|
|
77
test/integration/cleanup_gce.py
Normal file
77
test/integration/cleanup_gce.py
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
'''
|
||||||
|
Find and delete GCE resources matching the provided --match string. Unless
|
||||||
|
--yes|-y is provided, the prompt for confirmation prior to deleting resources.
|
||||||
|
Please use caution, you can easily delete your *ENTIRE* GCE infrastructure.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import optparse
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
try:
|
||||||
|
from libcloud.compute.types import Provider
|
||||||
|
from libcloud.compute.providers import get_driver
|
||||||
|
from libcloud.common.google import GoogleBaseError, QuotaExceededError, \
|
||||||
|
ResourceExistsError, ResourceInUseError, ResourceNotFoundError
|
||||||
|
_ = Provider.GCE
|
||||||
|
except ImportError:
|
||||||
|
print("failed=True " + \
|
||||||
|
"msg='libcloud with GCE support (0.13.3+) required for this module'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
import gce_credentials
|
||||||
|
|
||||||
|
|
||||||
|
def delete_gce_resources(get_func, attr, opts):
|
||||||
|
for item in get_func():
|
||||||
|
val = getattr(item, attr)
|
||||||
|
if re.search(opts.match_re, val, re.IGNORECASE):
|
||||||
|
prompt_and_delete(item, "Delete matching %s? [y/n]: " % (item,), opts.assumeyes)
|
||||||
|
|
||||||
|
def prompt_and_delete(item, prompt, assumeyes):
|
||||||
|
if not assumeyes:
|
||||||
|
assumeyes = raw_input(prompt).lower() == 'y'
|
||||||
|
assert hasattr(item, 'destroy'), "Class <%s> has no delete attribute" % item.__class__
|
||||||
|
if assumeyes:
|
||||||
|
item.destroy()
|
||||||
|
print ("Deleted %s" % item)
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = optparse.OptionParser(usage="%s [options]" % (sys.argv[0],),
|
||||||
|
description=__doc__)
|
||||||
|
gce_credentials.add_credentials_options(parser)
|
||||||
|
parser.add_option("--yes", "-y",
|
||||||
|
action="store_true", dest="assumeyes",
|
||||||
|
default=False,
|
||||||
|
help="Don't prompt for confirmation")
|
||||||
|
parser.add_option("--match",
|
||||||
|
action="store", dest="match_re",
|
||||||
|
default="^ansible-testing-",
|
||||||
|
help="Regular expression used to find GCE resources (default: %default)")
|
||||||
|
|
||||||
|
(opts, args) = parser.parse_args()
|
||||||
|
gce_credentials.check_required(opts, parser)
|
||||||
|
return (opts, args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
(opts, args) = parse_args()
|
||||||
|
|
||||||
|
# Connect to GCE
|
||||||
|
gce = gce_credentials.get_gce_driver(opts)
|
||||||
|
|
||||||
|
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:
|
||||||
|
print "\nExiting on user command."
|
|
@ -3,5 +3,10 @@
|
||||||
ec2_access_key:
|
ec2_access_key:
|
||||||
ec2_secret_key:
|
ec2_secret_key:
|
||||||
|
|
||||||
|
# GCE Credentials
|
||||||
|
service_account_email:
|
||||||
|
pem_file:
|
||||||
|
project_id:
|
||||||
|
|
||||||
# GITHUB SSH private key - a path to a SSH private key for use with github.com
|
# GITHUB SSH private key - a path to a SSH private key for use with github.com
|
||||||
github_ssh_private_key: "{{ lookup('env','HOME') }}/.ssh/id_rsa"
|
github_ssh_private_key: "{{ lookup('env','HOME') }}/.ssh/id_rsa"
|
||||||
|
|
6
test/integration/gce.yml
Normal file
6
test/integration/gce.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
- hosts: testhost
|
||||||
|
gather_facts: true
|
||||||
|
roles:
|
||||||
|
- { role: test_gce, tags: test_gce }
|
||||||
|
- { role: test_gce_pd, tags: test_gce_pd }
|
||||||
|
# TODO: tests for gce_lb, gce_net, gc_storage
|
51
test/integration/gce_credentials.py
Normal file
51
test/integration/gce_credentials.py
Normal 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)
|
6
test/integration/roles/test_gce/defaults/main.yml
Normal file
6
test/integration/roles/test_gce/defaults/main.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
# defaults file for test_gce
|
||||||
|
instance_name: "{{ resource_prefix|lower }}"
|
||||||
|
service_account_email: "{{ gce_service_account_email }}"
|
||||||
|
pem_file: "{{ gce_pem_file }}"
|
||||||
|
project_id: "{{ gce_project_id }}"
|
211
test/integration/roles/test_gce/tasks/main.yml
Normal file
211
test/integration/roles/test_gce/tasks/main.yml
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
# TODO: lots of attributes not covered: machine_type, zone, metadata, tags, etc.
|
||||||
|
#
|
||||||
|
# ============================================================
|
||||||
|
- name: test with no parameters
|
||||||
|
gce:
|
||||||
|
register: result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: assert failure when called with no parameters
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.failed'
|
||||||
|
- 'result.msg == "Missing GCE connection parameters in libcloud secrets file."'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test missing name
|
||||||
|
gce:
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
register: result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: assert failure when called with no parameters
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.failed'
|
||||||
|
- 'result.msg == "Must specify a \"name\" or \"instance_names\""'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test state=present (expected changed=true)
|
||||||
|
gce:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert state=present (expected changed=true)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'result.name == "{{ instance_name }}"'
|
||||||
|
- 'result.state == "present"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test state=present (expected changed=false)
|
||||||
|
gce:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert state=present (expected changed=false)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'not result.changed'
|
||||||
|
- 'result.name == "{{ instance_name }}"'
|
||||||
|
- 'result.state == "present"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test state=absent (expected changed=true)
|
||||||
|
gce:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert state=absent (expected changed=true)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'result.name == "{{ instance_name }}"'
|
||||||
|
- 'result.state == "absent"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test state=absent (expected changed=false)
|
||||||
|
gce:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert state=absent (expected changed=false)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'not result.changed'
|
||||||
|
- 'result.name == "{{ instance_name }}"'
|
||||||
|
- 'result.state == "absent"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test disks given (expected changed=true)
|
||||||
|
gce:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
disks:
|
||||||
|
- "{{ instance_name }}-base"
|
||||||
|
- "{{ instance_name }}-extra"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert disks given
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'result.instance_data[0].disks == ["{{ instance_name }}-base", "{{ instance_name }}-extra"]'
|
||||||
|
- 'result.state == "present"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test disks given (expected changed=false)
|
||||||
|
gce:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
disks:
|
||||||
|
- "{{ instance_name }}-base"
|
||||||
|
- "{{ instance_name }}-extra"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert disks given
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'not result.changed'
|
||||||
|
- 'result.instance_data[0].disks == ["{{ instance_name }}-base", "{{ instance_name }}-extra"]'
|
||||||
|
- 'result.state == "present"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test disks in the wrong order
|
||||||
|
gce:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
disks:
|
||||||
|
- "{{ instance_name }}-extra"
|
||||||
|
- "{{ instance_name }}-base"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
register: result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: assert disks in the wrong order
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.failed'
|
||||||
|
- '{{ result.msg | match("Disk at index 0 does not match:.*") }}'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test disks given with name and mode
|
||||||
|
gce:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
disks:
|
||||||
|
- { name: "{{ instance_name }}-base", mode: "READ_WRITE" }
|
||||||
|
- { name: "{{ instance_name }}-extra", mode: "READ_ONLY" }
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert disks given
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'not result.changed'
|
||||||
|
- 'result.state == "present"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test disks given with name and wrong mode
|
||||||
|
gce:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
disks:
|
||||||
|
- { name: "{{ instance_name }}-base", mode: "READ_ONLY" }
|
||||||
|
- "{{ instance_name }}-extra"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
register: result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: assert disks given
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.failed'
|
||||||
|
- '{{ result.msg | match("Disk at index 0 is in the wrong mode:.*") }}'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test disks given, state absent (expected changed=true)
|
||||||
|
gce:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
disks:
|
||||||
|
- "{{ instance_name }}-base"
|
||||||
|
- "{{ instance_name }}-extra"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert disks given, state absent (expected changed=true)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'result.state == "absent"'
|
6
test/integration/roles/test_gce_pd/defaults/main.yml
Normal file
6
test/integration/roles/test_gce_pd/defaults/main.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
# defaults file for test_gce
|
||||||
|
instance_name: "{{ resource_prefix|lower }}"
|
||||||
|
service_account_email: "{{ gce_service_account_email }}"
|
||||||
|
pem_file: "{{ gce_pem_file }}"
|
||||||
|
project_id: "{{ gce_project_id }}"
|
220
test/integration/roles/test_gce_pd/tasks/main.yml
Normal file
220
test/integration/roles/test_gce_pd/tasks/main.yml
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
# TODO: need tests for read/write mode.
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test missing name
|
||||||
|
gce_pd:
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
register: result
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: assert failure when called with no parameters
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.failed'
|
||||||
|
- 'result.msg == "missing required arguments: name"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test state=present (expected changed=true)
|
||||||
|
gce_pd:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert state=present (expected changed=true)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'result.name == "{{ instance_name }}"'
|
||||||
|
- 'result.size_gb == 10' # default size
|
||||||
|
- 'result.zone == "us-central1-b"' # default zone
|
||||||
|
- 'result.state == "present"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test state=present (expected changed=false)
|
||||||
|
gce_pd:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert state=present (expected changed=false)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'not result.changed'
|
||||||
|
- 'result.name == "{{ instance_name }}"'
|
||||||
|
- 'result.state == "present"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test state=absent (expected changed=true)
|
||||||
|
gce_pd:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert state=absent (expected changed=true)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'result.name == "{{ instance_name }}"'
|
||||||
|
- 'result.state == "absent"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test state=absent (expected changed=false)
|
||||||
|
gce_pd:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert state=absent (expected changed=false)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'not result.changed'
|
||||||
|
- 'result.name == "{{ instance_name }}"'
|
||||||
|
- 'result.state == "absent"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test non-default size/zone
|
||||||
|
gce_pd:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
size_gb: 5
|
||||||
|
zone: us-central1-a
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert non-default size/zone
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'result.name == "{{ instance_name }}"'
|
||||||
|
- 'result.size_gb == 5'
|
||||||
|
- 'result.zone == "us-central1-a"'
|
||||||
|
- 'result.state == "present"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test non-default size/zone (state=absent)
|
||||||
|
gce_pd:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
size_gb: 5
|
||||||
|
zone: us-central1-a
|
||||||
|
service_account_email: "{{ service_account_email }}"
|
||||||
|
pem_file: "{{ pem_file }}"
|
||||||
|
project_id: "{{ project_id }}"
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert non-default size/zone (state=absent)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'result.name == "{{ instance_name }}"'
|
||||||
|
- 'result.state == "absent"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test image given (state=present)
|
||||||
|
gce_pd:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
image: debian-7
|
||||||
|
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.image == "debian-7"'
|
||||||
|
- 'result.state == "present"'
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: test image given (state=absent)
|
||||||
|
gce_pd:
|
||||||
|
name: "{{ instance_name }}"
|
||||||
|
image: debian-7
|
||||||
|
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 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)"'
|
||||||
|
|
42
test/integration/setup_gce.py
Normal file
42
test/integration/setup_gce.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
'''
|
||||||
|
Create GCE resources for use in integration tests.
|
||||||
|
|
||||||
|
Takes a prefix as a command-line argument and creates two persistent disks named
|
||||||
|
${prefix}-base and ${prefix}-extra and a snapshot of the base disk 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')
|
||||||
|
gce.create_volume(
|
||||||
|
size=10, name=prefix+'-extra', location='us-central1-a')
|
||||||
|
except KeyboardInterrupt, e:
|
||||||
|
print "\nExiting on user command."
|
Loading…
Reference in a new issue