adding Google Compute Engine modules and inventory plugin
This commit is contained in:
parent
a2ac5d6df2
commit
9ca824c16d
7 changed files with 2294 additions and 0 deletions
417
library/cloud/gce
Normal file
417
library/cloud/gce
Normal file
|
@ -0,0 +1,417 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# Copyright 2013 Google Inc.
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: gce
|
||||||
|
short_description: create or terminate GCE instances
|
||||||
|
description:
|
||||||
|
- Creates or terminates Google Compute Engine (GCE) instances. See
|
||||||
|
U(https://cloud.google.com/products/compute-engine) for an overview.
|
||||||
|
Full install/configuration instructions for the gce* modules can
|
||||||
|
be found in the comments of ansible/test/gce_tests.py.
|
||||||
|
options:
|
||||||
|
image:
|
||||||
|
description:
|
||||||
|
- image string to use for the instance
|
||||||
|
required: false
|
||||||
|
default: "debian-7"
|
||||||
|
aliases: []
|
||||||
|
instance_names:
|
||||||
|
description:
|
||||||
|
- a comma-separated list of instance names to create or destroy
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
aliases: []
|
||||||
|
machine_type:
|
||||||
|
description:
|
||||||
|
- machine type to use for the instance, use 'n1-standard-1' by default
|
||||||
|
required: false
|
||||||
|
default: "n1-standard-1"
|
||||||
|
aliases: []
|
||||||
|
metadata:
|
||||||
|
description:
|
||||||
|
- a hash/dictionary of custom data for the instance; '{"key":"value",...}'
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
aliases: []
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- instance name (or name prefix) to be used for each created instance
|
||||||
|
required: false
|
||||||
|
default: "gce"
|
||||||
|
aliases: []
|
||||||
|
network:
|
||||||
|
description:
|
||||||
|
- name of the network, 'default' will be used if not specified
|
||||||
|
required: false
|
||||||
|
default: "default"
|
||||||
|
aliases: []
|
||||||
|
persistent_boot_disk:
|
||||||
|
description:
|
||||||
|
- if set, create the instance with a persistent boot disk
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
aliases: []
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- desired state of the resource
|
||||||
|
required: false
|
||||||
|
default: "present"
|
||||||
|
choices: ["active", "present", "absent", "deleted"]
|
||||||
|
aliases: []
|
||||||
|
tags:
|
||||||
|
description:
|
||||||
|
- a comma-separated list of tags to associate with the instance
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
aliases: []
|
||||||
|
zone:
|
||||||
|
description:
|
||||||
|
- the GCE zone to use
|
||||||
|
required: true
|
||||||
|
default: "us-central1-a"
|
||||||
|
choices: ["us-central1-a", "us-central1-b", "us-central2-a", "europe-west1-a", "europe-west1-b"]
|
||||||
|
aliases: []
|
||||||
|
|
||||||
|
requirements: [ "libcloud" ]
|
||||||
|
author: Eric Johnson <erjohnso@google.com>
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
# Basic provisioning example. Create a single Debian 7 instance in the
|
||||||
|
# us-central1-a Zone of n1-standard-1 machine type.
|
||||||
|
- local_action:
|
||||||
|
module: gce
|
||||||
|
name: test-instance
|
||||||
|
zone: us-central1-a
|
||||||
|
machine_type: n1-standard-1
|
||||||
|
image: debian-7
|
||||||
|
|
||||||
|
# Example using defaults and with metadata to create a single 'foo' instance
|
||||||
|
- local_action:
|
||||||
|
module: gce
|
||||||
|
name: foo
|
||||||
|
metadata: '{"db":"postgres", "group":"qa", "id":500}'
|
||||||
|
|
||||||
|
|
||||||
|
# Launch instances from a control node, runs some tasks on the new instances,
|
||||||
|
# and then terminate them
|
||||||
|
- name: Create a sandbox instance
|
||||||
|
hosts: localhost
|
||||||
|
vars:
|
||||||
|
names: foo,bar
|
||||||
|
machine_type: n1-standard-1
|
||||||
|
image: debian-6
|
||||||
|
zone: us-central1-a
|
||||||
|
tasks:
|
||||||
|
- name: Launch instances
|
||||||
|
local_action: gce instance_names=${names} machine_type=${machine_type}
|
||||||
|
image=${image} zone=${zone}
|
||||||
|
register: gce
|
||||||
|
- name: Wait for SSH to come up
|
||||||
|
local_action: wait_for host=${item.public_ip} port=22 delay=10
|
||||||
|
timeout=60 state=started
|
||||||
|
with_items: ${gce.instance_data}
|
||||||
|
|
||||||
|
- name: Configure instance(s)
|
||||||
|
hosts: launched
|
||||||
|
sudo: True
|
||||||
|
roles:
|
||||||
|
- my_awesome_role
|
||||||
|
- my_awesome_tasks
|
||||||
|
|
||||||
|
- name: Terminate instances
|
||||||
|
hosts: localhost
|
||||||
|
connection: local
|
||||||
|
tasks:
|
||||||
|
- name: Terminate instances that were previously launched
|
||||||
|
local_action:
|
||||||
|
module: gce
|
||||||
|
state: 'absent'
|
||||||
|
instance_names: ${gce.instance_names}
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ast import literal_eval
|
||||||
|
except ImportError:
|
||||||
|
print("failed=True " + \
|
||||||
|
"msg='GCE module requires python's 'ast' module, python v2.6+'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Load in the libcloud secrets file
|
||||||
|
try:
|
||||||
|
import secrets
|
||||||
|
except ImportError:
|
||||||
|
secrets = None
|
||||||
|
ARGS = getattr(secrets, 'GCE_PARAMS', ())
|
||||||
|
KWARGS = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
|
||||||
|
|
||||||
|
if not ARGS or not KWARGS.has_key('project'):
|
||||||
|
print("failed=True " + \
|
||||||
|
"msg='Missing GCE connection parametres in libcloud secrets file.'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def unexpected_error_msg(error):
|
||||||
|
"""Create an error string based on passed in error."""
|
||||||
|
msg='Unexpected response: HTTP return_code['
|
||||||
|
msg+='%s], API error code[%s] and message: %s' % (
|
||||||
|
error.http_code, error.code, str(error.value))
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def get_instance_info(inst):
|
||||||
|
"""Retrieves instance information from an instance object and returns it
|
||||||
|
as a dictionary.
|
||||||
|
|
||||||
|
"""
|
||||||
|
metadata = {}
|
||||||
|
if inst.extra.has_key('metadata') and inst.extra['metadata'].has_key('items'):
|
||||||
|
for md in inst.extra['metadata']['items']:
|
||||||
|
metadata[md['key']] = md['value']
|
||||||
|
|
||||||
|
try:
|
||||||
|
netname = inst.extra['networkInterfaces'][0]['network'].split('/')[-1]
|
||||||
|
except:
|
||||||
|
netname = None
|
||||||
|
return({
|
||||||
|
'image': not inst.image is None and inst.image.split('/')[-1] or None,
|
||||||
|
'machine_type': inst.size,
|
||||||
|
'metadata': metadata,
|
||||||
|
'name': inst.name,
|
||||||
|
'network': netname,
|
||||||
|
'private_ip': inst.private_ip[0],
|
||||||
|
'public_ip': inst.public_ip[0],
|
||||||
|
'status': inst.extra.has_key('status') and inst.extra['status'] or None,
|
||||||
|
'tags': inst.extra.has_key('tags') and inst.extra['tags'] or [],
|
||||||
|
'zone': inst.extra.has_key('zone') and inst.extra['zone'].name or None,
|
||||||
|
})
|
||||||
|
|
||||||
|
def create_instances(module, gce, instance_names):
|
||||||
|
"""Creates new instances. Attributes other than instance_names are picked
|
||||||
|
up from 'module'
|
||||||
|
|
||||||
|
module : AnsbileModule object
|
||||||
|
gce: authenticated GCE libcloud driver
|
||||||
|
instance_names: python list of instance names to create
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of dictionaries with instance information
|
||||||
|
about the instances that were launched.
|
||||||
|
|
||||||
|
"""
|
||||||
|
image = module.params.get('image')
|
||||||
|
machine_type = module.params.get('machine_type')
|
||||||
|
metadata = module.params.get('metadata')
|
||||||
|
network = module.params.get('network')
|
||||||
|
persistent_boot_disk = module.params.get('persistent_boot_disk')
|
||||||
|
state = module.params.get('state')
|
||||||
|
tags = module.params.get('tags')
|
||||||
|
zone = module.params.get('zone')
|
||||||
|
|
||||||
|
new_instances = []
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
lc_image = gce.ex_get_image(image)
|
||||||
|
lc_network = gce.ex_get_network(network)
|
||||||
|
lc_machine_type = gce.ex_get_size(machine_type)
|
||||||
|
lc_zone = gce.ex_get_zone(zone)
|
||||||
|
|
||||||
|
# Try to convert the user's metadata value into the format expected
|
||||||
|
# by GCE. First try to ensure user has proper quoting of a
|
||||||
|
# dictionary-like syntax using 'literal_eval', then convert the python
|
||||||
|
# dict into a python list of 'key' / 'value' dicts. Should end up
|
||||||
|
# with:
|
||||||
|
# [ {'key': key1, 'value': value1}, {'key': key2, 'value': value2}, ...]
|
||||||
|
if metadata:
|
||||||
|
try:
|
||||||
|
md = literal_eval(metadata)
|
||||||
|
if not isinstance(md, dict):
|
||||||
|
raise ValueError('metadata must be a dict')
|
||||||
|
except ValueError as e:
|
||||||
|
print("failed=True msg='bad metadata: %s'" % str(e))
|
||||||
|
sys.exit(1)
|
||||||
|
except SyntaxError as e:
|
||||||
|
print("failed=True msg='bad metadata syntax'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for k,v in md.items():
|
||||||
|
items.append({"key": k,"value": v})
|
||||||
|
metadata = {'items': items}
|
||||||
|
|
||||||
|
# These variables all have default values but check just in case
|
||||||
|
if not lc_image or not lc_network or not lc_machine_type or not lc_zone:
|
||||||
|
module.fail_json(msg='Missing required create instance variable',
|
||||||
|
changed=False)
|
||||||
|
|
||||||
|
for name in instance_names:
|
||||||
|
pd = None
|
||||||
|
if persistent_boot_disk:
|
||||||
|
try:
|
||||||
|
pd = gce.create_volume(None, "%s" % name, image=lc_image)
|
||||||
|
except ResourceExistsError:
|
||||||
|
pd = gce.ex_get_volume("%s" % name, lc_zone)
|
||||||
|
inst = None
|
||||||
|
try:
|
||||||
|
inst = gce.create_node(name, lc_machine_type, lc_image,
|
||||||
|
location=lc_zone, ex_network=network, ex_tags=tags,
|
||||||
|
ex_metadata=metadata, ex_boot_disk=pd)
|
||||||
|
changed = True
|
||||||
|
except ResourceExistsError:
|
||||||
|
inst = gce.ex_get_node(name, lc_zone)
|
||||||
|
except GoogleBaseError as e:
|
||||||
|
module.fail_json(msg='Unexpected error attempting to create ' + \
|
||||||
|
'instance %s, error: %s' % (name, e.value))
|
||||||
|
|
||||||
|
if inst:
|
||||||
|
new_instances.append(inst)
|
||||||
|
|
||||||
|
instance_names = []
|
||||||
|
instance_json_data = []
|
||||||
|
for inst in new_instances:
|
||||||
|
d = get_instance_info(inst)
|
||||||
|
instance_names.append(d['name'])
|
||||||
|
instance_json_data.append(d)
|
||||||
|
|
||||||
|
return (changed, instance_json_data, instance_names)
|
||||||
|
|
||||||
|
|
||||||
|
def terminate_instances(module, gce, instance_names, zone_name):
|
||||||
|
"""Terminates a list of instances.
|
||||||
|
|
||||||
|
module: Ansible module object
|
||||||
|
gce: authenticated GCE connection object
|
||||||
|
instance_names: a list of instance names to terminate
|
||||||
|
zone_name: the zone where the instances reside prior to termination
|
||||||
|
|
||||||
|
Returns a dictionary of instance names that were terminated.
|
||||||
|
|
||||||
|
"""
|
||||||
|
changed = False
|
||||||
|
terminated_instance_names = []
|
||||||
|
for name in instance_names:
|
||||||
|
inst = None
|
||||||
|
try:
|
||||||
|
inst = gce.ex_get_node(name, zone_name)
|
||||||
|
except ResourceNotFoundError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
if inst:
|
||||||
|
gce.destroy_node(inst)
|
||||||
|
terminated_instance_names.append(inst.name)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
return (changed, terminated_instance_names)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec = dict(
|
||||||
|
image = dict(default='debian-7'),
|
||||||
|
instance_names = dict(),
|
||||||
|
machine_type = dict(default='n1-standard-1'),
|
||||||
|
metadata = dict(),
|
||||||
|
name = dict(),
|
||||||
|
network = dict(default='default'),
|
||||||
|
persistent_boot_disk = dict(choices=BOOLEANS, default=False),
|
||||||
|
state = dict(choices=['active', 'present', 'absent', 'deleted'],
|
||||||
|
default='present'),
|
||||||
|
tags = dict(type='list'),
|
||||||
|
zone = dict(choices=['us-central1-a', 'us-central1-b',
|
||||||
|
'us-central2-a', 'europe-west1-a', 'europe-west1-b'],
|
||||||
|
default='us-central1-a'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
image = module.params.get('image')
|
||||||
|
instance_names = module.params.get('instance_names')
|
||||||
|
machine_type = module.params.get('machine_type')
|
||||||
|
metadata = module.params.get('metadata')
|
||||||
|
name = module.params.get('name')
|
||||||
|
network = module.params.get('network')
|
||||||
|
persistent_boot_disk = module.params.get('persistent_boot_disk')
|
||||||
|
state = module.params.get('state')
|
||||||
|
tags = module.params.get('tags')
|
||||||
|
zone = module.params.get('zone')
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
gce = get_driver(Provider.GCE)(*ARGS, datacenter=zone, **KWARGS)
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
|
||||||
|
inames = []
|
||||||
|
if isinstance(instance_names, list):
|
||||||
|
inames = instance_names
|
||||||
|
elif isinstance(instance_names, str):
|
||||||
|
inames = instance_names.split(',')
|
||||||
|
if name:
|
||||||
|
inames.append(name)
|
||||||
|
if not inames:
|
||||||
|
module.fail_json(msg='Must specify a "name" or "instance_names"',
|
||||||
|
changed=False)
|
||||||
|
if not zone:
|
||||||
|
module.fail_json(msg='Must specify a "zone"', changed=False)
|
||||||
|
|
||||||
|
json_output = {'zone': zone}
|
||||||
|
if state in ['absent', 'deleted']:
|
||||||
|
json_output['state'] = 'absent'
|
||||||
|
(changed, terminated_instance_names) = terminate_instances(module,
|
||||||
|
gce, inames, zone)
|
||||||
|
|
||||||
|
# based on what user specified, return the same variable, although
|
||||||
|
# value could be different if an instance could not be destroyed
|
||||||
|
if instance_names:
|
||||||
|
json_output['instance_names'] = terminated_instance_names
|
||||||
|
elif name:
|
||||||
|
json_output['name'] = name
|
||||||
|
|
||||||
|
elif state in ['active', 'present']:
|
||||||
|
json_output['state'] = 'present'
|
||||||
|
(changed, instance_data,instance_name_list) = create_instances(
|
||||||
|
module, gce, inames)
|
||||||
|
json_output['instance_data'] = instance_data
|
||||||
|
if instance_names:
|
||||||
|
json_output['instance_names'] = instance_name_list
|
||||||
|
elif name:
|
||||||
|
json_output['name'] = name
|
||||||
|
|
||||||
|
|
||||||
|
json_output['changed'] = changed
|
||||||
|
print json.dumps(json_output)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# this is magic, see lib/ansible/module_common.py
|
||||||
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||||
|
|
||||||
|
main()
|
325
library/cloud/gce_lb
Normal file
325
library/cloud/gce_lb
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# Copyright 2013 Google Inc.
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: gce_lb
|
||||||
|
short_description: create/destroy GCE load-balancer resources
|
||||||
|
description:
|
||||||
|
- This module can create and destroy Google Compue Engine C(loadbalancer)
|
||||||
|
and C(httphealthcheck) resources. The primary LB resource is the
|
||||||
|
C(load_balancer) resource and the health check parameters are all
|
||||||
|
prefixed with I(httphealthcheck).
|
||||||
|
The full documentation for Google Compute Engine load balancing is at
|
||||||
|
U(https://developers.google.com/compute/docs/load-balancing/). However,
|
||||||
|
the ansible module simplifies the configuration by following the
|
||||||
|
libcloud model.
|
||||||
|
Full install/configuration instructions for the gce* modules can
|
||||||
|
be found in the comments of ansible/test/gce_tests.py.
|
||||||
|
options:
|
||||||
|
httphealthcheck_name:
|
||||||
|
description:
|
||||||
|
- the name identifier for the HTTP health check
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
httphealthcheck_port:
|
||||||
|
description:
|
||||||
|
- the TCP port to use for HTTP health checking
|
||||||
|
required: false
|
||||||
|
default: 80
|
||||||
|
httphealthcheck_path:
|
||||||
|
description:
|
||||||
|
- the url path to use for HTTP health checking
|
||||||
|
required: false
|
||||||
|
default: "/"
|
||||||
|
httphealthcheck_interval:
|
||||||
|
description:
|
||||||
|
- the duration in seconds between each health check request
|
||||||
|
required: false
|
||||||
|
default: 5
|
||||||
|
httphealthcheck_timeout:
|
||||||
|
description:
|
||||||
|
- the timeout in seconds before a request is considered a failed check
|
||||||
|
required: false
|
||||||
|
default: 5
|
||||||
|
httphealthcheck_unhealthy_count:
|
||||||
|
description:
|
||||||
|
- number of consecutive failed checks before marking a node unhealthy
|
||||||
|
required: false
|
||||||
|
default: 2
|
||||||
|
httphealthcheck_healthy_count:
|
||||||
|
description:
|
||||||
|
- number of consecutive successful checks before marking a node healthy
|
||||||
|
required: false
|
||||||
|
default: 2
|
||||||
|
httphealthcheck_host:
|
||||||
|
description:
|
||||||
|
- host header to pass through on HTTP check requests
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- name of the load-balancer resource
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
protocol:
|
||||||
|
description:
|
||||||
|
- the protocol used for the load-balancer packet forwarding, tcp or udp
|
||||||
|
required: false
|
||||||
|
default: "tcp"
|
||||||
|
choices: ['tcp', 'udp']
|
||||||
|
region:
|
||||||
|
description:
|
||||||
|
- the GCE region where the load-balancer is defined
|
||||||
|
required: false
|
||||||
|
choices: ["us-central1", "us-central2", "europe-west1"]
|
||||||
|
external_ip:
|
||||||
|
description:
|
||||||
|
- the external static IPv4 (or auto-assigned) address for the LB
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
port_range:
|
||||||
|
description:
|
||||||
|
- the port (range) to forward, e.g. 80 or 8000-8888 defaults to all ports
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
members:
|
||||||
|
description:
|
||||||
|
- a list of zone/nodename pairs, e.g ['us-central1-a/www-a', ...]
|
||||||
|
required: false
|
||||||
|
aliases: ['nodes']
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- desired state of the LB
|
||||||
|
default: "present"
|
||||||
|
choices: ["active", "present", "absent", "deleted"]
|
||||||
|
aliases: []
|
||||||
|
|
||||||
|
requirements: [ "libcloud" ]
|
||||||
|
author: Eric Johnson <erjohnso@google.com>
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
# Simple example of creating a new LB, adding members, and a health check
|
||||||
|
- local_action:
|
||||||
|
module: gce_lb
|
||||||
|
name: testlb
|
||||||
|
region: us-central1
|
||||||
|
members: ["us-central1-a/www-a", "us-central1-b/www-b"]
|
||||||
|
httphealthcheck_name: hc
|
||||||
|
httphealthcheck_port: 80
|
||||||
|
httphealthcheck_path: "/up"
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
from libcloud.compute.types import Provider
|
||||||
|
from libcloud.compute.providers import get_driver
|
||||||
|
from libcloud.loadbalancer.types import Provider as Provider_lb
|
||||||
|
from libcloud.loadbalancer.providers import get_driver as get_driver_lb
|
||||||
|
from libcloud.common.google import GoogleBaseError, QuotaExceededError, \
|
||||||
|
ResourceExistsError, ResourceNotFoundError
|
||||||
|
_ = Provider.GCE
|
||||||
|
except ImportError:
|
||||||
|
print("failed=True " + \
|
||||||
|
"msg='libcloud with GCE support required for this module.'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Load in the libcloud secrets file
|
||||||
|
try:
|
||||||
|
import secrets
|
||||||
|
except ImportError:
|
||||||
|
secrets = None
|
||||||
|
ARGS = getattr(secrets, 'GCE_PARAMS', ())
|
||||||
|
KWARGS = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
|
||||||
|
|
||||||
|
if not ARGS or not KWARGS.has_key('project'):
|
||||||
|
print("failed=True msg='Missing GCE connection " + \
|
||||||
|
"parameters in libcloud secrets file.'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def unexpected_error_msg(error):
|
||||||
|
"""Format error string based on passed in error."""
|
||||||
|
msg='Unexpected response: HTTP return_code['
|
||||||
|
msg+='%s], API error code[%s] and message: %s' % (
|
||||||
|
error.http_code, error.code, str(error.value))
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec = dict(
|
||||||
|
httphealthcheck_name = dict(),
|
||||||
|
httphealthcheck_port = dict(default=80),
|
||||||
|
httphealthcheck_path = dict(default='/'),
|
||||||
|
httphealthcheck_interval = dict(default=5),
|
||||||
|
httphealthcheck_timeout = dict(default=5),
|
||||||
|
httphealthcheck_unhealthy_count = dict(default=2),
|
||||||
|
httphealthcheck_healthy_count = dict(default=2),
|
||||||
|
httphealthcheck_host = dict(),
|
||||||
|
name = dict(),
|
||||||
|
protocol = dict(default='tcp'),
|
||||||
|
region = dict(),
|
||||||
|
external_ip = dict(),
|
||||||
|
port_range = dict(),
|
||||||
|
members = dict(type='list'),
|
||||||
|
state = dict(default='present'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
httphealthcheck_name = module.params.get('httphealthcheck_name')
|
||||||
|
httphealthcheck_port = module.params.get('httphealthcheck_port')
|
||||||
|
httphealthcheck_path = module.params.get('httphealthcheck_path')
|
||||||
|
httphealthcheck_interval = module.params.get('httphealthcheck_interval')
|
||||||
|
httphealthcheck_timeout = module.params.get('httphealthcheck_timeout')
|
||||||
|
httphealthcheck_unhealthy_count = \
|
||||||
|
module.params.get('httphealthcheck_unhealthy_count')
|
||||||
|
httphealthcheck_healthy_count = \
|
||||||
|
module.params.get('httphealthcheck_healthy_count')
|
||||||
|
httphealthcheck_host = module.params.get('httphealthcheck_host')
|
||||||
|
name = module.params.get('name')
|
||||||
|
protocol = module.params.get('protocol')
|
||||||
|
region = module.params.get('region')
|
||||||
|
external_ip = module.params.get('external_ip')
|
||||||
|
port_range = module.params.get('port_range')
|
||||||
|
members = module.params.get('members')
|
||||||
|
state = module.params.get('state')
|
||||||
|
|
||||||
|
try:
|
||||||
|
gce = get_driver(Provider.GCE)(*ARGS, **KWARGS)
|
||||||
|
gcelb = get_driver_lb(Provider_lb.GCE)(gce_driver=gce)
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
json_output = {'name': name, 'state': state}
|
||||||
|
|
||||||
|
if not name and not httphealthcheck_name:
|
||||||
|
module.fail_json(msg='Nothing to do, please specify a "name" ' + \
|
||||||
|
'or "httphealthcheck_name" parameter', changed=False)
|
||||||
|
|
||||||
|
if state in ['active', 'present']:
|
||||||
|
# first, create the httphealthcheck if requested
|
||||||
|
hc = None
|
||||||
|
if httphealthcheck_name:
|
||||||
|
json_output['httphealthcheck_name'] = httphealthcheck_name
|
||||||
|
try:
|
||||||
|
hc = gcelb.ex_create_healthcheck(httphealthcheck_name,
|
||||||
|
host=httphealthcheck_host, path=httphealthcheck_path,
|
||||||
|
port=httphealthcheck_port,
|
||||||
|
interval=httphealthcheck_interval,
|
||||||
|
timeout=httphealthcheck_timeout,
|
||||||
|
unhealthy_threshold=httphealthcheck_unhealthy_count,
|
||||||
|
healthy_threshold=httphealthcheck_healthy_count)
|
||||||
|
changed = True
|
||||||
|
except ResourceExistsError:
|
||||||
|
hc = gce.ex_get_healthcheck(httphealthcheck_name)
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
|
||||||
|
if hc is not None:
|
||||||
|
json_output['httphealthcheck_host'] = hc.extra['host']
|
||||||
|
json_output['httphealthcheck_path'] = hc.path
|
||||||
|
json_output['httphealthcheck_port'] = hc.port
|
||||||
|
json_output['httphealthcheck_interval'] = hc.interval
|
||||||
|
json_output['httphealthcheck_timeout'] = hc.timeout
|
||||||
|
json_output['httphealthcheck_unhealthy_count'] = \
|
||||||
|
hc.unhealthy_threshold
|
||||||
|
json_output['httphealthcheck_healthy_count'] = \
|
||||||
|
hc.healthy_threshold
|
||||||
|
|
||||||
|
# create the forwarding rule (and target pool under the hood)
|
||||||
|
lb = None
|
||||||
|
if name:
|
||||||
|
if not region:
|
||||||
|
module.fail_json(msg='Missing required region name',
|
||||||
|
changed=False)
|
||||||
|
nodes = []
|
||||||
|
output_nodes = []
|
||||||
|
json_output['name'] = name
|
||||||
|
# members is a python list of 'zone/inst' strings
|
||||||
|
if members:
|
||||||
|
for node in members:
|
||||||
|
try:
|
||||||
|
zone, node_name = node.split('/')
|
||||||
|
nodes.append(gce.ex_get_node(node_name, zone))
|
||||||
|
output_nodes.append(node)
|
||||||
|
except:
|
||||||
|
# skip nodes that are badly formatted or don't exist
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
if hc is not None:
|
||||||
|
lb = gcelb.create_balancer(name, port_range, protocol,
|
||||||
|
None, nodes, ex_region=region, ex_healthchecks=[hc],
|
||||||
|
ex_address=external_ip)
|
||||||
|
else:
|
||||||
|
lb = gcelb.create_balancer(name, port_range, protocol,
|
||||||
|
None, nodes, ex_region=region, ex_address=external_ip)
|
||||||
|
changed = True
|
||||||
|
except ResourceExistsError:
|
||||||
|
lb = gcelb.get_balancer(name)
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
|
||||||
|
if lb is not None:
|
||||||
|
json_output['members'] = output_nodes
|
||||||
|
json_output['protocol'] = protocol
|
||||||
|
json_output['region'] = region
|
||||||
|
json_output['external_ip'] = lb.ip
|
||||||
|
json_output['port_range'] = lb.port
|
||||||
|
hc_names = []
|
||||||
|
if lb.extra.has_key('healthchecks'):
|
||||||
|
for hc in lb.extra['healthchecks']:
|
||||||
|
hc_names.append(hc.name)
|
||||||
|
json_output['httphealthchecks'] = hc_names
|
||||||
|
|
||||||
|
if state in ['absent', 'deleted']:
|
||||||
|
# first, delete the load balancer (forwarding rule and target pool)
|
||||||
|
# if specified.
|
||||||
|
if name:
|
||||||
|
json_output['name'] = name
|
||||||
|
try:
|
||||||
|
lb = gcelb.get_balancer(name)
|
||||||
|
gcelb.destroy_balancer(lb)
|
||||||
|
changed = True
|
||||||
|
except ResourceNotFoundError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
|
||||||
|
# destroy the health check if specified
|
||||||
|
if httphealthcheck_name:
|
||||||
|
json_output['httphealthcheck_name'] = httphealthcheck_name
|
||||||
|
try:
|
||||||
|
hc = gce.ex_get_healthcheck(httphealthcheck_name)
|
||||||
|
gce.ex_destroy_healthcheck(hc)
|
||||||
|
changed = True
|
||||||
|
except ResourceNotFoundError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
|
||||||
|
|
||||||
|
json_output['changed'] = changed
|
||||||
|
print json.dumps(json_output)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# this is magic, see lib/ansible/module_common.py
|
||||||
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||||
|
|
||||||
|
main()
|
267
library/cloud/gce_net
Normal file
267
library/cloud/gce_net
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# Copyright 2013 Google Inc.
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: gce_net
|
||||||
|
short_description: create/destroy GCE networks and firewall rules
|
||||||
|
description:
|
||||||
|
- This module can create and destroy Google Compue Engine networks and
|
||||||
|
firewall rules U(https://developers.google.com/compute/docs/networking).
|
||||||
|
The I(name) parameter is reserved for referencing a network while the
|
||||||
|
I(fwname) parameter is used to reference firewall rules.
|
||||||
|
IPv4 Address ranges must be specified using the CIDR
|
||||||
|
U(http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) format.
|
||||||
|
Full install/configuration instructions for the gce* modules can
|
||||||
|
be found in the comments of ansible/test/gce_tests.py.
|
||||||
|
options:
|
||||||
|
allowed:
|
||||||
|
description:
|
||||||
|
- the protocol:ports to allow ('tcp:80' or 'tcp:80,443' or 'tcp:80-800')
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
aliases: []
|
||||||
|
ipv4_range:
|
||||||
|
description:
|
||||||
|
- the IPv4 address range in CIDR notation for the network
|
||||||
|
required: false
|
||||||
|
aliases: ['cidr']
|
||||||
|
fwname:
|
||||||
|
description:
|
||||||
|
- name of the firewall rule
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
aliases: ['fwrule']
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- name of the network
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
aliases: []
|
||||||
|
src_range:
|
||||||
|
description:
|
||||||
|
- the source IPv4 address range in CIDR notation
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
aliases: ['src_cidr']
|
||||||
|
src_tags:
|
||||||
|
description:
|
||||||
|
- the source instance tags for creating a firewall rule
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
aliases: []
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- desired state of the persistent disk
|
||||||
|
required: false
|
||||||
|
default: "present"
|
||||||
|
choices: ["active", "present", "absent", "deleted"]
|
||||||
|
aliases: []
|
||||||
|
|
||||||
|
requirements: [ "libcloud" ]
|
||||||
|
author: Eric Johnson <erjohnso@google.com>
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
# Simple example of creating a new network
|
||||||
|
- local_action:
|
||||||
|
module: gce_net
|
||||||
|
name: privatenet
|
||||||
|
ipv4_range: '10.240.16.0/24'
|
||||||
|
|
||||||
|
# Simple example of creating a new firewall rule
|
||||||
|
- local_action:
|
||||||
|
module: gce_net
|
||||||
|
name: privatenet
|
||||||
|
allowed: tcp:80,8080
|
||||||
|
src_tags: ["web", "proxy"]
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
from libcloud.compute.types import Provider
|
||||||
|
from libcloud.compute.providers import get_driver
|
||||||
|
from libcloud.common.google import GoogleBaseError, QuotaExceededError, \
|
||||||
|
ResourceExistsError, ResourceNotFoundError
|
||||||
|
_ = Provider.GCE
|
||||||
|
except ImportError:
|
||||||
|
print("failed=True " + \
|
||||||
|
"msg='libcloud with GCE support required for this module.'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Load in the libcloud secrets file
|
||||||
|
try:
|
||||||
|
import secrets
|
||||||
|
except ImportError:
|
||||||
|
secrets = None
|
||||||
|
ARGS = getattr(secrets, 'GCE_PARAMS', ())
|
||||||
|
KWARGS = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
|
||||||
|
|
||||||
|
if not ARGS or not KWARGS.has_key('project'):
|
||||||
|
print("failed=True msg='Missing GCE connection " + \
|
||||||
|
"parameters in libcloud secrets file.'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def unexpected_error_msg(error):
|
||||||
|
"""Format error string based on passed in error."""
|
||||||
|
msg='Unexpected response: HTTP return_code['
|
||||||
|
msg+='%s], API error code[%s] and message: %s' % (
|
||||||
|
error.http_code, error.code, str(error.value))
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def format_allowed(allowed):
|
||||||
|
"""Format the 'allowed' value so that it is GCE compatible."""
|
||||||
|
if allowed.count(":") == 0:
|
||||||
|
protocol = allowed
|
||||||
|
ports = []
|
||||||
|
elif allowed.count(":") == 1:
|
||||||
|
protocol, ports = allowed.split(":")
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
if ports.count(","):
|
||||||
|
ports = ports.split(",")
|
||||||
|
else:
|
||||||
|
ports = [ports]
|
||||||
|
return_val = {"IPProtocol": protocol}
|
||||||
|
if ports:
|
||||||
|
return_val["ports"] = ports
|
||||||
|
return [return_val]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec = dict(
|
||||||
|
allowed = dict(),
|
||||||
|
ipv4_range = dict(),
|
||||||
|
fwname = dict(),
|
||||||
|
name = dict(),
|
||||||
|
src_range = dict(),
|
||||||
|
src_tags = dict(type='list'),
|
||||||
|
state = dict(default='present'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
allowed = module.params.get('allowed')
|
||||||
|
ipv4_range = module.params.get('ipv4_range')
|
||||||
|
fwname = module.params.get('fwname')
|
||||||
|
name = module.params.get('name')
|
||||||
|
src_range = module.params.get('src_range')
|
||||||
|
src_tags = module.params.get('src_tags')
|
||||||
|
state = module.params.get('state')
|
||||||
|
|
||||||
|
try:
|
||||||
|
gce = get_driver(Provider.GCE)(*ARGS, **KWARGS)
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
json_output = {'state': state}
|
||||||
|
|
||||||
|
if state in ['active', 'present']:
|
||||||
|
network = None
|
||||||
|
try:
|
||||||
|
network = gce.ex_get_network(name)
|
||||||
|
json_output['name'] = name
|
||||||
|
json_output['ipv4_range'] = network.cidr
|
||||||
|
except ResourceNotFoundError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
|
||||||
|
# user wants to create a new network that doesn't yet exist
|
||||||
|
if name and not network:
|
||||||
|
if not ipv4_range:
|
||||||
|
module.fail_json(msg="Missing required 'ipv4_range' parameter",
|
||||||
|
changed=False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
network = gce.ex_create_network(name, ipv4_range)
|
||||||
|
json_output['name'] = name
|
||||||
|
json_output['ipv4_range'] = ipv4_range
|
||||||
|
changed = True
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
|
||||||
|
if fwname:
|
||||||
|
# user creating a firewall rule
|
||||||
|
if not allowed and not src_range and not src_tags:
|
||||||
|
if changed and network:
|
||||||
|
module.fail_json(
|
||||||
|
msg="Network created, but missing required " + \
|
||||||
|
"firewall rule parameter(s)", changed=True)
|
||||||
|
module.fail_json(
|
||||||
|
msg="Missing required firewall rule parameter(s)",
|
||||||
|
changed=False)
|
||||||
|
|
||||||
|
allowed_list = format_allowed(allowed)
|
||||||
|
|
||||||
|
try:
|
||||||
|
gce.ex_create_firewall(fwname, allowed_list, network=name,
|
||||||
|
source_ranges=src_range, source_tags=src_tags)
|
||||||
|
changed = True
|
||||||
|
except ResourceExistsError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
|
||||||
|
json_output['fwname'] = fwname
|
||||||
|
json_output['allowed'] = allowed
|
||||||
|
json_output['src_range'] = src_range
|
||||||
|
json_output['src_tags'] = src_tags
|
||||||
|
|
||||||
|
if state in ['absent', 'deleted']:
|
||||||
|
if fwname:
|
||||||
|
json_output['fwname'] = fwname
|
||||||
|
fw = None
|
||||||
|
try:
|
||||||
|
fw = gce.ex_get_firewall(fwname)
|
||||||
|
except ResourceNotFoundError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
if fw:
|
||||||
|
gce.ex_destroy_firewall(fw)
|
||||||
|
changed = True
|
||||||
|
if name:
|
||||||
|
json_output['name'] = name
|
||||||
|
network = None
|
||||||
|
try:
|
||||||
|
network = gce.ex_get_network(name)
|
||||||
|
# json_output['d1'] = 'found network name %s' % name
|
||||||
|
except ResourceNotFoundError:
|
||||||
|
# json_output['d2'] = 'not found network name %s' % name
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
# json_output['d3'] = 'error with %s' % name
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
if network:
|
||||||
|
# json_output['d4'] = 'deleting %s' % name
|
||||||
|
gce.ex_destroy_network(network)
|
||||||
|
# json_output['d5'] = 'deleted %s' % name
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
json_output['changed'] = changed
|
||||||
|
print json.dumps(json_output)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# this is magic, see lib/ansible/module_common.py
|
||||||
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||||
|
|
||||||
|
main()
|
248
library/cloud/gce_pd
Normal file
248
library/cloud/gce_pd
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# Copyright 2013 Google Inc.
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: gce_pd
|
||||||
|
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.
|
||||||
|
Full install/configuration instructions for the gce* modules can
|
||||||
|
be found in the comments of ansible/test/gce_tests.py.
|
||||||
|
options:
|
||||||
|
detach_only:
|
||||||
|
description:
|
||||||
|
- do not destroy the disk, merely detach it from an instance
|
||||||
|
required: false
|
||||||
|
default: "no"
|
||||||
|
choices: ["yes", "no"]
|
||||||
|
aliases: []
|
||||||
|
instance_name:
|
||||||
|
description:
|
||||||
|
- instance name if you wish to attach or detach the disk
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
aliases: []
|
||||||
|
mode:
|
||||||
|
description:
|
||||||
|
- GCE mount mode of disk, READ_ONLY (default) or READ_WRITE
|
||||||
|
required: false
|
||||||
|
default: "READ_ONLY"
|
||||||
|
choices: ["READ_WRITE", "READ_ONLY"]
|
||||||
|
aliases: []
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- name of the disk
|
||||||
|
required: true
|
||||||
|
default: null
|
||||||
|
aliases: []
|
||||||
|
size_gb:
|
||||||
|
description:
|
||||||
|
- whole integer size of disk (in GB) to create, default is 10 GB
|
||||||
|
required: false
|
||||||
|
default: 10
|
||||||
|
aliases: []
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- desired state of the persistent disk
|
||||||
|
required: false
|
||||||
|
default: "present"
|
||||||
|
choices: ["active", "present", "absent", "deleted"]
|
||||||
|
aliases: []
|
||||||
|
zone:
|
||||||
|
description:
|
||||||
|
- zone in which to create the disk
|
||||||
|
required: false
|
||||||
|
default: "us-central1-b"
|
||||||
|
aliases: []
|
||||||
|
|
||||||
|
requirements: [ "libcloud" ]
|
||||||
|
author: Eric Johnson <erjohnso@google.com>
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
# Simple attachment action to an existing instance
|
||||||
|
- local_action:
|
||||||
|
module: gce_pd
|
||||||
|
instance_name: notlocalhost
|
||||||
|
size_gb: 5
|
||||||
|
name: pd
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
from libcloud.compute.types import Provider
|
||||||
|
from libcloud.compute.providers import get_driver
|
||||||
|
from libcloud.common.google import GoogleBaseError, QuotaExceededError, \
|
||||||
|
ResourceExistsError, ResourceNotFoundError, ResourceInUseError
|
||||||
|
_ = Provider.GCE
|
||||||
|
except ImportError:
|
||||||
|
print("failed=True " + \
|
||||||
|
"msg='libcloud with GCE support is required for this module.'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Load in the libcloud secrets file
|
||||||
|
try:
|
||||||
|
import secrets
|
||||||
|
except ImportError:
|
||||||
|
secrets = None
|
||||||
|
ARGS = getattr(secrets, 'GCE_PARAMS', ())
|
||||||
|
KWARGS = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
|
||||||
|
|
||||||
|
if not ARGS or not KWARGS.has_key('project'):
|
||||||
|
print("failed=True " + \
|
||||||
|
"msg='Missing GCE connection parameters in libcloud secrets file.'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def unexpected_error_msg(error):
|
||||||
|
msg='Unexpected response: HTTP return_code['
|
||||||
|
msg+='%s], API error code[%s] and message: %s' % (
|
||||||
|
error.http_code, error.code, str(error.value))
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def main():
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec = dict(
|
||||||
|
detach_only = dict(choice=BOOLEANS),
|
||||||
|
instance_name = dict(),
|
||||||
|
mode = dict(default='READ_ONLY',
|
||||||
|
choices=['READ_WRITE', 'READ_ONLY']),
|
||||||
|
name = dict(required=True),
|
||||||
|
size_gb = dict(default=10),
|
||||||
|
state = dict(default='present'),
|
||||||
|
zone = dict(default='us-central1-b'),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
detach_only = module.params.get('detach_only')
|
||||||
|
instance_name = module.params.get('instance_name')
|
||||||
|
mode = module.params.get('mode')
|
||||||
|
name = module.params.get('name')
|
||||||
|
size_gb = module.params.get('size_gb')
|
||||||
|
state = module.params.get('state')
|
||||||
|
zone = module.params.get('zone')
|
||||||
|
|
||||||
|
if detach_only and not instance_name:
|
||||||
|
module.fail_json(
|
||||||
|
msg='Must specify an instance name when detaching a disk',
|
||||||
|
changed=False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
gce = get_driver(Provider.GCE)(*ARGS, datacenter=zone, **KWARGS)
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
|
||||||
|
disk = inst = None
|
||||||
|
changed = is_attached = False
|
||||||
|
|
||||||
|
json_output = { 'name': name, 'zone': zone, 'state': state }
|
||||||
|
if detach_only:
|
||||||
|
json_output['detach_only'] = True
|
||||||
|
json_output['detached_from_instance'] = instance_name
|
||||||
|
|
||||||
|
if instance_name:
|
||||||
|
# user wants to attach/detach from an existing instance
|
||||||
|
try:
|
||||||
|
inst = gce.ex_get_node(instance_name, zone)
|
||||||
|
# is the disk attached?
|
||||||
|
for d in inst.extra['disks']:
|
||||||
|
if d['deviceName'] == name:
|
||||||
|
is_attached = True
|
||||||
|
json_output['attached_mode'] = d['mode']
|
||||||
|
json_output['attached_to_instance'] = inst.name
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# find disk if it already exists
|
||||||
|
try:
|
||||||
|
disk = gce.ex_get_volume(name)
|
||||||
|
json_output['size_gb'] = int(disk.size)
|
||||||
|
except ResourceNotFoundError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
|
||||||
|
# user wants a disk to exist. If "instance_name" is supplied the user
|
||||||
|
# also wants it attached
|
||||||
|
if state in ['active', 'present']:
|
||||||
|
|
||||||
|
if not size_gb:
|
||||||
|
module.fail_json(msg="Must supply a size_gb", changed=False)
|
||||||
|
try:
|
||||||
|
size_gb = int(round(float(size_gb)))
|
||||||
|
if size_gb < 1: raise Exception
|
||||||
|
except:
|
||||||
|
module.fail_json(msg="Must supply a size_gb larger than 1 GB",
|
||||||
|
changed=False)
|
||||||
|
|
||||||
|
if instance_name and inst is None:
|
||||||
|
module.fail_json(msg='Instance %s does not exist in zone %s' % (
|
||||||
|
instance_name, zone), changed=False)
|
||||||
|
|
||||||
|
if not disk:
|
||||||
|
try:
|
||||||
|
disk = gce.create_volume(size_gb, name, location=zone)
|
||||||
|
except ResourceExistsError:
|
||||||
|
pass
|
||||||
|
except QuotaExceededError:
|
||||||
|
module.fail_json(msg='Requested disk size exceeds quota',
|
||||||
|
changed=False)
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
json_output['size_gb'] = size_gb
|
||||||
|
changed = True
|
||||||
|
if inst and not is_attached:
|
||||||
|
try:
|
||||||
|
gce.attach_volume(inst, disk, device=name, ex_mode=mode)
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
json_output['attached_to_instance'] = inst.name
|
||||||
|
json_output['attached_mode'] = mode
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
# user wants to delete a disk (or perhaps just detach it).
|
||||||
|
if state in ['absent', 'deleted'] and disk:
|
||||||
|
|
||||||
|
if inst and is_attached:
|
||||||
|
try:
|
||||||
|
gce.detach_volume(disk, ex_node=inst)
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
changed = True
|
||||||
|
if not detach_only:
|
||||||
|
try:
|
||||||
|
gce.destroy_volume(disk)
|
||||||
|
except ResourceInUseError as e:
|
||||||
|
module.fail_json(msg=str(e.value), changed=False)
|
||||||
|
except Exception as e:
|
||||||
|
module.fail_json(msg=unexpected_error_msg(e), changed=False)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
json_output['changed'] = changed
|
||||||
|
print json.dumps(json_output)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# this is magic, see lib/ansible/module_common.py
|
||||||
|
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||||
|
|
||||||
|
main()
|
47
plugins/inventory/gce.ini
Normal file
47
plugins/inventory/gce.ini
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# Copyright 2013 Google Inc.
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# The GCE inventory script has the following dependencies:
|
||||||
|
# 1. A valid Google Cloud Platform account with Google Compute Engine
|
||||||
|
# enabled. See https://cloud.google.com
|
||||||
|
# 2. An OAuth2 Service Account flow should be enabled. This will generate
|
||||||
|
# a private key file that the inventory script will use for API request
|
||||||
|
# authorization. See https://developers.google.com/accounts/docs/OAuth2
|
||||||
|
# 3. Convert the private key from PKCS12 to PEM format
|
||||||
|
# $ openssl pkcs12 -in pkey.pkcs12 -passin pass:notasecret \
|
||||||
|
# > -nodes -nocerts | openssl rsa -out pkey.pem
|
||||||
|
# 4. The libcloud (>=0.13.3) python libray. See http://libcloud.apache.org
|
||||||
|
#
|
||||||
|
# (See ansible/test/gce_tests.py comments for full install instructions)
|
||||||
|
#
|
||||||
|
# Author: Eric Johnson <erjohnso@google.com>
|
||||||
|
|
||||||
|
[gce]
|
||||||
|
# GCE Service Account configuration information can be stored in the
|
||||||
|
# libcloud 'secrets.py' file. Ideally, the 'secrets.py' file will already
|
||||||
|
# exist in your PYTHONPATH and be picked up automatically with an import
|
||||||
|
# statement in the inventory script. However, you can specify an absolute
|
||||||
|
# path to the secrets.py file with 'libcloud_secrets' parameter.
|
||||||
|
libcloud_secrets =
|
||||||
|
|
||||||
|
# If you are not going to use a 'secrets.py' file, you can set the necessary
|
||||||
|
# authorization parameters here.
|
||||||
|
gce_service_account_email_address =
|
||||||
|
gce_service_account_pem_file_path =
|
||||||
|
gce_project_id =
|
||||||
|
|
238
plugins/inventory/gce.py
Executable file
238
plugins/inventory/gce.py
Executable file
|
@ -0,0 +1,238 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# Copyright 2013 Google Inc.
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
'''
|
||||||
|
GCE external inventory script
|
||||||
|
=================================
|
||||||
|
|
||||||
|
Generates inventory that Ansible can understand by making API requests
|
||||||
|
Google Compute Engine via the libcloud library. Full install/configuration
|
||||||
|
instructions for the gce* modules can be found in the comments of
|
||||||
|
ansible/test/gce_tests.py.
|
||||||
|
|
||||||
|
When run against a specific host, this script returns the following variables
|
||||||
|
based on the data obtained from the libcloud Node object:
|
||||||
|
- gce_uuid
|
||||||
|
- gce_id
|
||||||
|
- gce_image
|
||||||
|
- gce_machine_type
|
||||||
|
- gce_private_ip
|
||||||
|
- gce_public_ip
|
||||||
|
- gce_name
|
||||||
|
- gce_description
|
||||||
|
- gce_status
|
||||||
|
- gce_zone
|
||||||
|
- gce_tags
|
||||||
|
- gce_metadata
|
||||||
|
- gce_network
|
||||||
|
|
||||||
|
When run in --list mode, instances are grouped by the following categories:
|
||||||
|
- zone:
|
||||||
|
zone group name examples are us-central1-b, europe-west1-a, etc.
|
||||||
|
- instance tags:
|
||||||
|
An entry is created for each tag. For example, if you have two instances
|
||||||
|
with a common tag called 'foo', they will both be grouped together under
|
||||||
|
the 'tag_foo' name.
|
||||||
|
- network name:
|
||||||
|
the name of the network is appended to 'network_' (e.g. the 'default'
|
||||||
|
network will result in a group named 'network_default')
|
||||||
|
- machine type
|
||||||
|
types follow a pattern like n1-standard-4, g1-small, etc.
|
||||||
|
- running status:
|
||||||
|
group name prefixed with 'status_' (e.g. status_running, status_stopped,..)
|
||||||
|
- image:
|
||||||
|
when using an ephemeral/scratch disk, this will be set to the image name
|
||||||
|
used when creating the instance (e.g. debian-7-wheezy-v20130816). when
|
||||||
|
your instance was created with a root persistent disk it will be set to
|
||||||
|
'persistent_disk' since there is no current way to determine the image.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Execute uname on all instances in the us-central1-a zone
|
||||||
|
$ ansible -i gce.py us-central1-a -m shell -a "/bin/uname -a"
|
||||||
|
|
||||||
|
Use the GCE inventory script to print out instance specific information
|
||||||
|
$ plugins/inventory/gce.py --host my_instance
|
||||||
|
|
||||||
|
Author: Eric Johnson <erjohnso@google.com>
|
||||||
|
Version: 0.0.1
|
||||||
|
'''
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import ConfigParser
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
try:
|
||||||
|
from libcloud.compute.types import Provider
|
||||||
|
from libcloud.compute.providers import get_driver
|
||||||
|
_ = Provider.GCE
|
||||||
|
except:
|
||||||
|
print("GCE inventory script requires libcloud >= 0.13")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
class GceInventory(object):
|
||||||
|
def __init__(self):
|
||||||
|
# Read settings and parse CLI arguments
|
||||||
|
self.parse_cli_args()
|
||||||
|
self.driver = self.get_gce_driver()
|
||||||
|
|
||||||
|
# Just display data for specific host
|
||||||
|
if self.args.host:
|
||||||
|
print self.json_format_dict(self.node_to_dict(
|
||||||
|
self.get_instance(self.args.host)))
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# Otherwise, assume user wants all instances grouped
|
||||||
|
print self.json_format_dict(self.group_instances())
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def get_gce_driver(self):
|
||||||
|
'''Determine GCE authorization settings and return libcloud driver.'''
|
||||||
|
|
||||||
|
config = ConfigParser.SafeConfigParser()
|
||||||
|
config.read(os.path.dirname(os.path.realpath(__file__)) + '/gce.ini')
|
||||||
|
|
||||||
|
# the GCE params in 'secrets.py' will override these
|
||||||
|
secrets_path = config.get('gce', 'libcloud_secrets')
|
||||||
|
|
||||||
|
secrets_found = False
|
||||||
|
try:
|
||||||
|
import secrets
|
||||||
|
args = getattr(secrets, 'GCE_PARAMS', ())
|
||||||
|
kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
|
||||||
|
secrets_found = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not secrets_found and secrets_path:
|
||||||
|
if not secrets_path.endswith('secrets.py'):
|
||||||
|
err = "Must specify libcloud secrets file as "
|
||||||
|
err += "/absolute/path/to/secrets.py"
|
||||||
|
print(err)
|
||||||
|
sys.exit(1)
|
||||||
|
sys.path.append(os.path.dirname(secrets_path))
|
||||||
|
try:
|
||||||
|
import secrets
|
||||||
|
args = getattr(secrets, 'GCE_PARAMS', ())
|
||||||
|
kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {})
|
||||||
|
secrets_found = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if not secrets_found:
|
||||||
|
args = (
|
||||||
|
config.get('gce','gce_service_account_email_address'),
|
||||||
|
config.get('gce','gce_service_account_pem_file_path')
|
||||||
|
)
|
||||||
|
kwargs = {'project': config.get('gce','gce_project_id')}
|
||||||
|
|
||||||
|
return get_driver(Provider.GCE)(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_cli_args(self):
|
||||||
|
''' Command line argument processing '''
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Produce an Ansible Inventory file based on GCE')
|
||||||
|
parser.add_argument('--list', action='store_true', default=True,
|
||||||
|
help='List instances (default: True)')
|
||||||
|
parser.add_argument('--host', action='store',
|
||||||
|
help='Get all information about an instance')
|
||||||
|
self.args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def node_to_dict(self, inst):
|
||||||
|
md = {}
|
||||||
|
if inst.extra['metadata'].has_key('items'):
|
||||||
|
for entry in inst.extra['metadata']['items']:
|
||||||
|
md[entry['key']] = entry['value']
|
||||||
|
|
||||||
|
net = inst.extra['networkInterfaces'][0]['network'].split('/')[-1]
|
||||||
|
return {
|
||||||
|
'gce_uuid': inst.uuid,
|
||||||
|
'gce_id': inst.id,
|
||||||
|
'gce_image': inst.image,
|
||||||
|
'gce_machine_type': inst.size,
|
||||||
|
'gce_private_ip': inst.private_ip[0],
|
||||||
|
'gce_public_ip': inst.public_ip[0],
|
||||||
|
'gce_name': inst.name,
|
||||||
|
'gce_description': inst.extra['description'],
|
||||||
|
'gce_status': inst.extra['status'],
|
||||||
|
'gce_zone': inst.extra['zone'].name,
|
||||||
|
'gce_tags': inst.extra['tags'],
|
||||||
|
'gce_metadata': md,
|
||||||
|
'gce_network': net
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_instance(self, instance_name):
|
||||||
|
'''Gets details about a specific instance '''
|
||||||
|
return self.driver.ex_get_node(instance_name)
|
||||||
|
|
||||||
|
def group_instances(self):
|
||||||
|
'''Group all instances'''
|
||||||
|
groups = {}
|
||||||
|
for node in self.driver.list_nodes():
|
||||||
|
name = node.name
|
||||||
|
|
||||||
|
zone = node.extra['zone'].name
|
||||||
|
if groups.has_key(zone): groups[zone].append(name)
|
||||||
|
else: groups[zone] = [name]
|
||||||
|
|
||||||
|
tags = node.extra['tags']
|
||||||
|
for t in tags:
|
||||||
|
tag = 'tag_%s' % t
|
||||||
|
if groups.has_key(tag): groups[tag].append(name)
|
||||||
|
else: groups[tag] = [name]
|
||||||
|
|
||||||
|
net = node.extra['networkInterfaces'][0]['network'].split('/')[-1]
|
||||||
|
net = 'network_%s' % net
|
||||||
|
if groups.has_key(net): groups[net].append(name)
|
||||||
|
else: groups[net] = [name]
|
||||||
|
|
||||||
|
machine_type = node.size
|
||||||
|
if groups.has_key(machine_type): groups[machine_type].append(name)
|
||||||
|
else: groups[machine_type] = [name]
|
||||||
|
|
||||||
|
image = node.image and node.image or 'persistent_disk'
|
||||||
|
if groups.has_key(image): groups[image].append(name)
|
||||||
|
else: groups[image] = [name]
|
||||||
|
|
||||||
|
status = node.extra['status']
|
||||||
|
stat = 'status_%s' % status.lower()
|
||||||
|
if groups.has_key(stat): groups[stat].append(name)
|
||||||
|
else: groups[stat] = [name]
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def json_format_dict(self, data, pretty=False):
|
||||||
|
''' Converts a dict to a JSON object and dumps it as a formatted
|
||||||
|
string '''
|
||||||
|
|
||||||
|
if pretty:
|
||||||
|
return json.dumps(data, sort_keys=True, indent=2)
|
||||||
|
else:
|
||||||
|
return json.dumps(data)
|
||||||
|
|
||||||
|
|
||||||
|
# Run the script
|
||||||
|
GceInventory()
|
752
test/gce_tests.py
Normal file
752
test/gce_tests.py
Normal file
|
@ -0,0 +1,752 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# Copyright 2013 Google Inc.
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# This is a custom functional test script for the Google Compute Engine
|
||||||
|
# ansible modules. In order to run these tests, you must:
|
||||||
|
# 1) Create a Google Cloud Platform account and enable the Google
|
||||||
|
# Compute Engine service and billing
|
||||||
|
# 2) Download, install, and configure 'gcutil'
|
||||||
|
# see [https://developers.google.com/compute/docs/gcutil/]
|
||||||
|
# 3) Convert your GCE Service Account private key from PKCS12 to PEM format
|
||||||
|
# $ openssl pkcs12 -in pkey.pkcs12 -passin pass:notasecret \
|
||||||
|
# > -nodes -nocerts | openssl rsa -out pkey.pem
|
||||||
|
# 4) Make sure you have libcloud 0.13.3 or later installed.
|
||||||
|
# 5) Make sure you have a libcloud 'secrets.py' file in your PYTHONPATH
|
||||||
|
# 6) Set GCE_PARAMS and GCE_KEYWORD_PARMS in your 'secrets.py' file.
|
||||||
|
# 7) Set up a simple hosts file
|
||||||
|
# $ echo 127.0.0.1 > ~/ansible_hosts
|
||||||
|
# $ echo "export ANSIBLE_HOSTS='~/ansible_hosts'" >> ~/.bashrc
|
||||||
|
# $ . ~/.bashrc
|
||||||
|
# 8) Set up your ansible 'hacking' environment
|
||||||
|
# $ cd ~/ansible
|
||||||
|
# $ . hacking/env-setup
|
||||||
|
# $ export ANSIBLE_HOST_KEY_CHECKING=no
|
||||||
|
# $ ansible all -m ping
|
||||||
|
# 9) Set your PROJECT variable below
|
||||||
|
# 10) Run and time the tests and log output, take ~30 minutes to run
|
||||||
|
# $ time stdbuf -oL python test/gce_tests.py 2>&1 | tee log
|
||||||
|
|
||||||
|
# Set this to your test Project ID
|
||||||
|
PROJECT="google.com:erjohnso"
|
||||||
|
|
||||||
|
# debugging
|
||||||
|
DEBUG=False # lots of debugging output
|
||||||
|
VERBOSE=True # on failure, display ansible command and expected/actual result
|
||||||
|
|
||||||
|
# location - note that some tests rely on the module's 'default'
|
||||||
|
# region/zone, which should match the settings below.
|
||||||
|
REGION="us-central1"
|
||||||
|
ZONE="%s-a" % REGION
|
||||||
|
|
||||||
|
# Peeking is a way to trigger looking at a specified set of resources
|
||||||
|
# before and/or after a test run. The 'test_cases' data structure below
|
||||||
|
# has a few tests with 'peek_before' and 'peek_after'. When those keys
|
||||||
|
# are set and PEEKING_ENABLED is True, then these steps will be executed
|
||||||
|
# to aid in debugging tests. Normally, this is not needed.
|
||||||
|
PEEKING_ENABLED=False
|
||||||
|
|
||||||
|
# disks
|
||||||
|
DNAME="aaaaa-ansible-disk"
|
||||||
|
DNAME2="aaaaa-ansible-disk2"
|
||||||
|
DNAME6="aaaaa-ansible-inst6"
|
||||||
|
DNAME7="aaaaa-ansible-inst7"
|
||||||
|
USE_PD="true"
|
||||||
|
KERNEL="https://www.googleapis.com/compute/v1beta15/projects/google/global/kernels/gce-v20130813"
|
||||||
|
|
||||||
|
# instances
|
||||||
|
INAME="aaaaa-ansible-inst"
|
||||||
|
INAME2="aaaaa-ansible-inst2"
|
||||||
|
INAME3="aaaaa-ansible-inst3"
|
||||||
|
INAME4="aaaaa-ansible-inst4"
|
||||||
|
INAME5="aaaaa-ansible-inst5"
|
||||||
|
INAME6="aaaaa-ansible-inst6"
|
||||||
|
INAME7="aaaaa-ansible-inst7"
|
||||||
|
TYPE="n1-standard-1"
|
||||||
|
IMAGE="https://www.googleapis.com/compute/v1beta15/projects/debian-cloud/global/images/debian-7-wheezy-v20130816"
|
||||||
|
NETWORK="default"
|
||||||
|
SCOPES="https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/compute,https://www.googleapis.com/auth/devstorage.full_control"
|
||||||
|
|
||||||
|
# networks / firewalls
|
||||||
|
NETWK1="ansible-network1"
|
||||||
|
NETWK2="ansible-network2"
|
||||||
|
NETWK3="ansible-network3"
|
||||||
|
CIDR1="10.240.16.0/24"
|
||||||
|
CIDR2="10.240.32.0/24"
|
||||||
|
CIDR3="10.240.64.0/24"
|
||||||
|
GW1="10.240.16.1"
|
||||||
|
GW2="10.240.32.1"
|
||||||
|
FW1="ansible-fwrule1"
|
||||||
|
FW2="ansible-fwrule2"
|
||||||
|
FW3="ansible-fwrule3"
|
||||||
|
FW4="ansible-fwrule4"
|
||||||
|
|
||||||
|
# load-balancer tests
|
||||||
|
HC1="ansible-hc1"
|
||||||
|
HC2="ansible-hc2"
|
||||||
|
HC3="ansible-hc3"
|
||||||
|
LB1="ansible-lb1"
|
||||||
|
LB2="ansible-lb2"
|
||||||
|
|
||||||
|
from commands import getstatusoutput as run
|
||||||
|
import sys
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
{'id': '01', 'desc': 'Detach / Delete disk tests',
|
||||||
|
'setup': ['gcutil addinstance "%s" --wait_until_running --zone=%s --machine_type=%s --network=%s --service_account_scopes="%s" --image="%s" --persistent_boot_disk=%s' % (INAME, ZONE, TYPE, NETWORK, SCOPES, IMAGE, USE_PD),
|
||||||
|
'gcutil adddisk "%s" --size_gb=2 --zone=%s' % (DNAME, ZONE)],
|
||||||
|
|
||||||
|
'tests': [
|
||||||
|
{'desc': 'DETACH_ONLY but disk not found [success]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s instance_name=%s zone=%s detach_only=yes state=absent' % ("missing-disk", INAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "detach_only": true, "detached_from_instance": "%s", "name": "missing-disk", "state": "absent", "zone": "%s"}' % (INAME, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'DETACH_ONLY but instance not found [success]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s instance_name=%s zone=%s detach_only=yes state=absent' % (DNAME, "missing-instance", ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "detach_only": true, "detached_from_instance": "missing-instance", "name": "%s", "size_gb": 2, "state": "absent", "zone": "%s"}' % (DNAME, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'DETACH_ONLY but neither disk nor instance exists [success]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s instance_name=%s zone=%s detach_only=yes state=absent' % ("missing-disk", "missing-instance", ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "detach_only": true, "detached_from_instance": "missing-instance", "name": "missing-disk", "state": "absent", "zone": "%s"}' % (ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'DETACH_ONLY but disk is not currently attached [success]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s instance_name=%s zone=%s detach_only=yes state=absent' % (DNAME, INAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "detach_only": true, "detached_from_instance": "%s", "name": "%s", "size_gb": 2, "state": "absent", "zone": "%s"}' % (INAME, DNAME, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'DETACH_ONLY disk is attached and should be detached [success]',
|
||||||
|
'setup': ['gcutil attachdisk --disk="%s,mode=READ_ONLY" --zone=%s %s' % (DNAME, ZONE, INAME), 'sleep 10'],
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s instance_name=%s zone=%s detach_only=yes state=absent' % (DNAME, INAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"attached_mode": "READ_ONLY", "attached_to_instance": "%s", "changed": true, "detach_only": true, "detached_from_instance": "%s", "name": "%s", "size_gb": 2, "state": "absent", "zone": "%s"}' % (INAME, INAME, DNAME, ZONE),
|
||||||
|
'teardown': ['gcutil detachdisk --zone=%s --device_name=%s %s' % (ZONE, DNAME, INAME)],
|
||||||
|
},
|
||||||
|
{'desc': 'DETACH_ONLY but not instance specified [FAIL]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s zone=%s detach_only=yes state=absent' % (DNAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Must specify an instance name when detaching a disk"}',
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE but disk not found [success]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s zone=%s state=absent' % ("missing-disk", ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "name": "missing-disk", "state": "absent", "zone": "%s"}' % (ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE but disk is attached [FAIL]',
|
||||||
|
'setup': ['gcutil attachdisk --disk="%s,mode=READ_ONLY" --zone=%s %s' % (DNAME, ZONE, INAME), 'sleep 10'],
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s zone=%s state=absent' % (DNAME, ZONE),
|
||||||
|
'r': "127.0.0.1 | FAILED >> {\"changed\": false, \"failed\": true, \"msg\": \"The disk resource 'projects/%s/zones/%s/disks/%s' is already being used by 'projects/%s/zones/%s/instances/%s'\"}" % (PROJECT, ZONE, DNAME, PROJECT, ZONE, INAME),
|
||||||
|
'teardown': ['gcutil detachdisk --zone=%s --device_name=%s %s' % (ZONE, DNAME, INAME)],
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE disk [success]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s zone=%s state=absent' % (DNAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "name": "%s", "size_gb": 2, "state": "absent", "zone": "%s"}' % (DNAME, ZONE),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'teardown': ['gcutil deleteinstance -f "%s" --zone=%s' % (INAME, ZONE),
|
||||||
|
'sleep 15',
|
||||||
|
'gcutil deletedisk -f "%s" --zone=%s' % (INAME, ZONE),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil deletedisk -f "%s" --zone=%s' % (DNAME, ZONE),
|
||||||
|
'sleep 10'],
|
||||||
|
},
|
||||||
|
|
||||||
|
{'id': '02', 'desc': 'Create disk but do not attach (e.g. no instance_name param)',
|
||||||
|
'setup': [],
|
||||||
|
'tests': [
|
||||||
|
{'desc': 'CREATE_NO_ATTACH "string" for size_gb [FAIL]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s size_gb="foo" zone=%s' % (DNAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Must supply a size_gb larger than 1 GB"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_NO_ATTACH negative size_gb [FAIL]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s size_gb=-2 zone=%s' % (DNAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Must supply a size_gb larger than 1 GB"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_NO_ATTACH size_gb exceeds quota [FAIL]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s size_gb=9999 zone=%s' % ("big-disk", ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Requested disk size exceeds quota"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_NO_ATTACH create the disk [success]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s zone=%s' % (DNAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "name": "%s", "size_gb": 10, "state": "present", "zone": "%s"}' % (DNAME, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_NO_ATTACH but disk already exists [success]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s zone=%s' % (DNAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "name": "%s", "size_gb": 10, "state": "present", "zone": "%s"}' % (DNAME, ZONE),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'teardown': ['gcutil deletedisk -f "%s" --zone=%s' % (DNAME, ZONE),
|
||||||
|
'sleep 10'],
|
||||||
|
},
|
||||||
|
|
||||||
|
{'id': '03', 'desc': 'Create and attach disk',
|
||||||
|
'setup': ['gcutil addinstance "%s" --zone=%s --machine_type=%s --network=%s --service_account_scopes="%s" --image="%s" --persistent_boot_disk=%s' % (INAME2, ZONE, TYPE, NETWORK, SCOPES, IMAGE, USE_PD),
|
||||||
|
'gcutil addinstance "%s" --zone=%s --machine_type=%s --network=%s --service_account_scopes="%s" --image="%s" --persistent_boot_disk=%s' % (INAME, ZONE, "g1-small", NETWORK, SCOPES, IMAGE, USE_PD),
|
||||||
|
'gcutil adddisk "%s" --size_gb=2 --zone=%s' % (DNAME, ZONE),
|
||||||
|
'gcutil adddisk "%s" --size_gb=2 --zone=%s' % (DNAME2, ZONE),
|
||||||
|
'sleep 10'],
|
||||||
|
'tests': [
|
||||||
|
{'desc': 'CREATE_AND_ATTACH "string" for size_gb [FAIL]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s size_gb="foo" instance_name=%s zone=%s' % (DNAME, INAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Must supply a size_gb larger than 1 GB"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_AND_ATTACH negative size_gb [FAIL]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s size_gb=-2 instance_name=%s zone=%s' % (DNAME, INAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Must supply a size_gb larger than 1 GB"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_AND_ATTACH size_gb exceeds quota [FAIL]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s size_gb=9999 instance_name=%s zone=%s' % ("big-disk", INAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Requested disk size exceeds quota"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_AND_ATTACH missing instance [FAIL]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s instance_name=%s zone=%s' % (DNAME, "missing-instance", ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Instance %s does not exist in zone %s"}' % ("missing-instance", ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_AND_ATTACH disk exists but not attached [success]',
|
||||||
|
'peek_before': ["gcutil --format=csv listinstances --zone=%s --filter=\"name eq 'aaaa.*'\"" % (ZONE)],
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s instance_name=%s zone=%s' % (DNAME, INAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"attached_mode": "READ_ONLY", "attached_to_instance": "%s", "changed": true, "name": "%s", "size_gb": 2, "state": "present", "zone": "%s"}' % (INAME, DNAME, ZONE),
|
||||||
|
'peek_after': ["gcutil --format=csv listinstances --zone=%s --filter=\"name eq 'aaaa.*'\"" % (ZONE)],
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_AND_ATTACH disk exists already attached [success]',
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s instance_name=%s zone=%s' % (DNAME, INAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"attached_mode": "READ_ONLY", "attached_to_instance": "%s", "changed": false, "name": "%s", "size_gb": 2, "state": "present", "zone": "%s"}' % (INAME, DNAME, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_AND_ATTACH attached RO, attempt RO to 2nd inst [success]',
|
||||||
|
'peek_before': ["gcutil --format=csv listinstances --zone=%s --filter=\"name eq 'aaaa.*'\"" % (ZONE)],
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s instance_name=%s zone=%s' % (DNAME, INAME2, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"attached_mode": "READ_ONLY", "attached_to_instance": "%s", "changed": true, "name": "%s", "size_gb": 2, "state": "present", "zone": "%s"}' % (INAME2, DNAME, ZONE),
|
||||||
|
'peek_after': ["gcutil --format=csv listinstances --zone=%s --filter=\"name eq 'aaaa.*'\"" % (ZONE)],
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_AND_ATTACH attached RO, attach RW to self [FAILED no-op]',
|
||||||
|
'peek_before': ["gcutil --format=csv listinstances --zone=%s --filter=\"name eq 'aaaa.*'\"" % (ZONE)],
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s instance_name=%s zone=%s mode=READ_WRITE' % (DNAME, INAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"attached_mode": "READ_ONLY", "attached_to_instance": "%s", "changed": false, "name": "%s", "size_gb": 2, "state": "present", "zone": "%s"}' % (INAME, DNAME, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_AND_ATTACH attached RW, attach RW to other [FAIL]',
|
||||||
|
'setup': ['gcutil attachdisk --disk=%s,mode=READ_WRITE --zone=%s %s' % (DNAME2, ZONE, INAME), 'sleep 10'],
|
||||||
|
'peek_before': ["gcutil --format=csv listinstances --zone=%s --filter=\"name eq 'aaaa.*'\"" % (ZONE)],
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s instance_name=%s zone=%s mode=READ_WRITE' % (DNAME2, INAME2, ZONE),
|
||||||
|
'r': "127.0.0.1 | FAILED >> {\"changed\": false, \"failed\": true, \"msg\": \"Unexpected response: HTTP return_code[200], API error code[RESOURCE_IN_USE] and message: The disk resource 'projects/%s/zones/%s/disks/%s' is already being used in read-write mode\"}" % (PROJECT, ZONE, DNAME2),
|
||||||
|
'peek_after': ["gcutil --format=csv listinstances --zone=%s --filter=\"name eq 'aaaa.*'\"" % (ZONE)],
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_AND_ATTACH attach too many disks to inst [FAIL]',
|
||||||
|
'setup': ['gcutil adddisk aa-disk-dummy --size_gb=2 --zone=%s' % (ZONE),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil adddisk aa-disk-dummy2 --size_gb=2 --zone=%s' % (ZONE),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil attachdisk --disk=aa-disk-dummy --zone=%s %s' % (ZONE, INAME),
|
||||||
|
'sleep 5'],
|
||||||
|
'peek_before': ["gcutil --format=csv listinstances --zone=%s --filter=\"name eq 'aaaa.*'\"" % (ZONE)],
|
||||||
|
'm': 'gce_pd',
|
||||||
|
'a': 'name=%s instance_name=%s zone=%s' % ("aa-disk-dummy2", INAME, ZONE),
|
||||||
|
'r': "127.0.0.1 | FAILED >> {\"changed\": false, \"failed\": true, \"msg\": \"Unexpected response: HTTP return_code[200], API error code[LIMIT_EXCEEDED] and message: Exceeded limit 'maximum_persistent_disks' on resource 'projects/%s/zones/%s/instances/%s'. Limit: 4\"}" % (PROJECT, ZONE, INAME),
|
||||||
|
'teardown': ['gcutil detachdisk --device_name=aa-disk-dummy --zone=%s %s' % (ZONE, INAME),
|
||||||
|
'sleep 3',
|
||||||
|
'gcutil deletedisk -f aa-disk-dummy --zone=%s' % (ZONE),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil deletedisk -f aa-disk-dummy2 --zone=%s' % (ZONE),
|
||||||
|
'sleep 10'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'teardown': ['gcutil deleteinstance -f "%s" --zone=%s' % (INAME2, ZONE),
|
||||||
|
'sleep 15',
|
||||||
|
'gcutil deleteinstance -f "%s" --zone=%s' % (INAME, ZONE),
|
||||||
|
'sleep 15',
|
||||||
|
'gcutil deletedisk -f "%s" --zone=%s' % (INAME, ZONE),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil deletedisk -f "%s" --zone=%s' % (INAME2, ZONE),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil deletedisk -f "%s" --zone=%s' % (DNAME, ZONE),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil deletedisk -f "%s" --zone=%s' % (DNAME2, ZONE),
|
||||||
|
'sleep 10'],
|
||||||
|
},
|
||||||
|
|
||||||
|
{'id': '04', 'desc': 'Delete / destroy instances',
|
||||||
|
'setup': ['gcutil addinstance "%s" --zone=%s --machine_type=%s --image="%s" --persistent_boot_disk=false' % (INAME, ZONE, TYPE, IMAGE),
|
||||||
|
'gcutil addinstance "%s" --zone=%s --machine_type=%s --image="%s" --persistent_boot_disk=false' % (INAME2, ZONE, TYPE, IMAGE),
|
||||||
|
'gcutil addinstance "%s" --zone=%s --machine_type=%s --image="%s" --persistent_boot_disk=false' % (INAME3, ZONE, TYPE, IMAGE),
|
||||||
|
'gcutil addinstance "%s" --zone=%s --machine_type=%s --image="%s" --persistent_boot_disk=false' % (INAME4, ZONE, TYPE, IMAGE),
|
||||||
|
'gcutil addinstance "%s" --wait_until_running --zone=%s --machine_type=%s --image="%s" --persistent_boot_disk=false' % (INAME5, ZONE, TYPE, IMAGE)],
|
||||||
|
'tests': [
|
||||||
|
{'desc': 'DELETE instance, bad zone param [FAIL]',
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=missing-inst zone=bogus state=absent',
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"failed": true, "msg": "value of zone must be one of: us-central1-a,us-central1-b,us-central2-a,europe-west1-a,europe-west1-b, got: bogus"}',
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE non-existent instance, no-op [success]',
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=missing-inst zone=%s state=absent' % (ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "name": "missing-inst", "state": "absent", "zone": "%s"}' % (ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE an existing named instance [success]',
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s zone=%s state=absent' % (INAME, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "name": "%s", "state": "absent", "zone": "%s"}' % (INAME, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE list of instances with a non-existent one [success]',
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'instance_names=%s,missing,%s zone=%s state=absent' % (INAME2,INAME3, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "instance_names": ["%s", "%s"], "state": "absent", "zone": "%s"}' % (INAME2, INAME3, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE list of instances all pre-exist [success]',
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'instance_names=%s,%s zone=%s state=absent' % (INAME4,INAME5, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "instance_names": ["%s", "%s"], "state": "absent", "zone": "%s"}' % (INAME4, INAME5, ZONE),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'teardown': ['gcutil deleteinstance -f "%s" --zone=%s' % (INAME, ZONE),
|
||||||
|
'gcutil deleteinstance -f "%s" --zone=%s' % (INAME2, ZONE),
|
||||||
|
'gcutil deleteinstance -f "%s" --zone=%s' % (INAME3, ZONE),
|
||||||
|
'gcutil deleteinstance -f "%s" --zone=%s' % (INAME4, ZONE),
|
||||||
|
'gcutil deleteinstance -f "%s" --zone=%s' % (INAME5, ZONE),
|
||||||
|
'sleep 10'],
|
||||||
|
},
|
||||||
|
|
||||||
|
{'id': '05', 'desc': 'Create instances',
|
||||||
|
'setup': [],
|
||||||
|
'tests': [
|
||||||
|
{'desc': 'CREATE_INSTANCE invalid image arg [FAIL]',
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=foo image=foo',
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Missing required create instance variable"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE metadata a list [FAIL]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s zone=%s metadata=\'[\\"foo\\":\\"bar\\",\\"baz\\":1]\'' % (INAME,ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"failed": true, "msg": "bad metadata syntax"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE metadata not a dict [FAIL]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s zone=%s metadata=\\"foo\\":\\"bar\\",\\"baz\\":1' % (INAME,ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"failed": true, "msg": "bad metadata syntax"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE with metadata form1 [FAIL]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s zone=%s metadata=\'{"foo":"bar","baz":1}\'' % (INAME,ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"failed": true, "msg": "bad metadata: malformed string"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE with metadata form2 [FAIL]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s zone=%s metadata={\'foo\':\'bar\',\'baz\':1}' % (INAME,ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"failed": true, "msg": "bad metadata: malformed string"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE with metadata form3 [FAIL]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s zone=%s metadata="foo:bar" '% (INAME,ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"failed": true, "msg": "bad metadata syntax"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE with metadata form4 [FAIL]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s zone=%s metadata="{\'foo\':\'bar\'}"'% (INAME,ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"failed": true, "msg": "bad metadata: malformed string"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE invalid image arg [FAIL]',
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'instance_names=foo,bar image=foo',
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Missing required create instance variable"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE single inst, using defaults [success]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s' % (INAME),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "instance_data": [{"image": "debian-7-wheezy-v20130816", "machine_type": "n1-standard-1", "metadata": {}, "name": "%s", "network": "default", "private_ip": "10.240.175.15", "public_ip": "173.255.120.190", "status": "RUNNING", "tags": [], "zone": "%s"}], "name": "%s", "state": "present", "zone": "%s"}' % (INAME, ZONE, INAME, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE the same instance again, no-op [success]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s' % (INAME),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "instance_data": [{"image": "debian-7-wheezy-v20130816", "machine_type": "n1-standard-1", "metadata": {}, "name": "%s", "network": "default", "private_ip": "10.240.175.15", "public_ip": "173.255.120.190", "status": "RUNNING", "tags": [], "zone": "%s"}], "name": "%s", "state": "present", "zone": "%s"}' % (INAME, ZONE, INAME, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE instance with alt type [success]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s machine_type=n1-standard-2' % (INAME2),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "instance_data": [{"image": "debian-7-wheezy-v20130816", "machine_type": "n1-standard-2", "metadata": {}, "name": "%s", "network": "default", "private_ip": "10.240.192.227", "public_ip": "173.255.121.233", "status": "RUNNING", "tags": [], "zone": "%s"}], "name": "%s", "state": "present", "zone": "%s"}' % (INAME2, ZONE, INAME2, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE instance with root pd [success]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s persistent_boot_disk=yes' % (INAME3),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "instance_data": [{"image": null, "machine_type": "n1-standard-1", "metadata": {}, "name": "%s", "network": "default", "private_ip": "10.240.178.140", "public_ip": "173.255.121.176", "status": "RUNNING", "tags": [], "zone": "%s"}], "name": "%s", "state": "present", "zone": "%s"}' % (INAME3, ZONE, INAME3, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE instance with root pd, that already exists [success]',
|
||||||
|
'setup': ['gcutil adddisk --source_image=%s --zone=%s %s' % (IMAGE, ZONE, DNAME6),
|
||||||
|
'sleep 10'],
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s zone=%s persistent_boot_disk=yes' % (INAME6, ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "instance_data": [{"image": null, "machine_type": "n1-standard-1", "metadata": {}, "name": "%s", "network": "default", "private_ip": "10.240.178.140", "public_ip": "173.255.121.176", "status": "RUNNING", "tags": [], "zone": "%s"}], "name": "%s", "state": "present", "zone": "%s"}' % (INAME6, ZONE, INAME6, ZONE),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE instance with root pd attached to other inst [FAIL]',
|
||||||
|
'setup': ['gcutil adddisk --source_image=%s --zone=%s %s' % (IMAGE, ZONE, DNAME7),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil addinstance boo --wait_until_running --zone=%s --machine_type=%s --network=%s --disk=%s,mode=READ_WRITE,boot --kernel=%s' % (ZONE,TYPE,NETWORK,DNAME7,KERNEL),
|
||||||
|
],
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'name=%s zone=%s persistent_boot_disk=yes' % (INAME7, ZONE),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"failed": true, "msg": "Unexpected error attempting to create instance %s, error: The disk resource \'projects/%s/zones/%s/disks/%s\' is already being used in read-write mode"}' % (INAME7,PROJECT,ZONE,DNAME7),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_INSTANCE use *all* the options! [success]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce',
|
||||||
|
'a': 'instance_names=%s,%s metadata=\'{\\"foo\\":\\"bar\\", \\"baz\\":1}\' tags=t1,t2,t3 zone=%s image=centos-6-v20130731 persistent_boot_disk=yes' % (INAME4,INAME5,ZONE),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "instance_data": [{"image": null, "machine_type": "n1-standard-1", "metadata": {"baz": "1", "foo": "bar"}, "name": "%s", "network": "default", "private_ip": "10.240.130.4", "public_ip": "173.255.121.97", "status": "RUNNING", "tags": ["t1", "t2", "t3"], "zone": "%s"}, {"image": null, "machine_type": "n1-standard-1", "metadata": {"baz": "1", "foo": "bar"}, "name": "%s", "network": "default", "private_ip": "10.240.207.226", "public_ip": "173.255.121.85", "status": "RUNNING", "tags": ["t1", "t2", "t3"], "zone": "%s"}], "instance_names": ["%s", "%s"], "state": "present", "zone": "%s"}' % (INAME4, ZONE, INAME5, ZONE, INAME4, INAME5, ZONE),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'teardown': ['gcutil deleteinstance -f "%s" --zone=%s' % (INAME, ZONE),
|
||||||
|
'gcutil deleteinstance -f "%s" --zone=%s' % (INAME2, ZONE),
|
||||||
|
'gcutil deleteinstance -f "%s" --zone=%s' % (INAME3, ZONE),
|
||||||
|
'gcutil deleteinstance -f "%s" --zone=%s' % (INAME4, ZONE),
|
||||||
|
'gcutil deleteinstance -f "%s" --zone=%s' % (INAME5, ZONE),
|
||||||
|
'gcutil deleteinstance -f "%s" --zone=%s' % (INAME6, ZONE),
|
||||||
|
'gcutil deleteinstance -f "%s" --zone=%s' % (INAME7, ZONE),
|
||||||
|
'gcutil deleteinstance -f boo --zone=%s' % (ZONE),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil deletedisk -f "%s" --zone=%s' % (INAME3, ZONE),
|
||||||
|
'gcutil deletedisk -f "%s" --zone=%s' % (INAME4, ZONE),
|
||||||
|
'gcutil deletedisk -f "%s" --zone=%s' % (INAME5, ZONE),
|
||||||
|
'gcutil deletedisk -f "%s" --zone=%s' % (INAME6, ZONE),
|
||||||
|
'gcutil deletedisk -f "%s" --zone=%s' % (INAME7, ZONE),
|
||||||
|
'sleep 10'],
|
||||||
|
},
|
||||||
|
|
||||||
|
{'id': '06', 'desc': 'Delete / destroy networks and firewall rules',
|
||||||
|
'setup': ['gcutil addnetwork --range="%s" --gateway="%s" %s' % (CIDR1, GW1, NETWK1),
|
||||||
|
'gcutil addnetwork --range="%s" --gateway="%s" %s' % (CIDR2, GW2, NETWK2),
|
||||||
|
'sleep 5',
|
||||||
|
'gcutil addfirewall --allowed="tcp:80" --network=%s %s' % (NETWK1, FW1),
|
||||||
|
'gcutil addfirewall --allowed="tcp:80" --network=%s %s' % (NETWK2, FW2),
|
||||||
|
'sleep 5'],
|
||||||
|
'tests': [
|
||||||
|
{'desc': 'DELETE bogus named firewall [success]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'fwname=missing-fwrule state=absent',
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "fwname": "missing-fwrule", "state": "absent"}',
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE bogus named network [success]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'name=missing-network state=absent',
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "name": "missing-network", "state": "absent"}',
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE named firewall rule [success]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'fwname=%s state=absent' % (FW1),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "fwname": "%s", "state": "absent"}' % (FW1),
|
||||||
|
'teardown': ['sleep 5'], # pause to give GCE time to delete fwrule
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE unused named network [success]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'name=%s state=absent' % (NETWK1),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "name": "%s", "state": "absent"}' % (NETWK1),
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE named network *and* fwrule [success]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'name=%s fwname=%s state=absent' % (NETWK2, FW2),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "fwname": "%s", "name": "%s", "state": "absent"}' % (FW2, NETWK2),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'teardown': ['gcutil deletenetwork -f %s' % (NETWK1),
|
||||||
|
'gcutil deletenetwork -f %s' % (NETWK2),
|
||||||
|
'sleep 5',
|
||||||
|
'gcutil deletefirewall -f %s' % (FW1),
|
||||||
|
'gcutil deletefirewall -f %s' % (FW2)],
|
||||||
|
},
|
||||||
|
|
||||||
|
{'id': '07', 'desc': 'Create networks and firewall rules',
|
||||||
|
'setup': ['gcutil addnetwork --range="%s" --gateway="%s" %s' % (CIDR1, GW1, NETWK1),
|
||||||
|
'sleep 5',
|
||||||
|
'gcutil addfirewall --allowed="tcp:80" --network=%s %s' % (NETWK1, FW1),
|
||||||
|
'sleep 5'],
|
||||||
|
'tests': [
|
||||||
|
{'desc': 'CREATE network without specifying ipv4_range [FAIL]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'name=fail',
|
||||||
|
'r': "127.0.0.1 | FAILED >> {\"changed\": false, \"failed\": true, \"msg\": \"Missing required 'ipv4_range' parameter\"}",
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE network with specifying bad ipv4_range [FAIL]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'name=fail ipv4_range=bad_value',
|
||||||
|
'r': "127.0.0.1 | FAILED >> {\"changed\": false, \"failed\": true, \"msg\": \"Unexpected response: HTTP return_code[400], API error code[None] and message: Invalid value for field 'resource.IPv4Range': 'bad_value'. Must be a CIDR address range that is contained in the RFC1918 private address blocks: [10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16]\"}",
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE existing network, not changed [success]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'name=%s ipv4_range=%s' % (NETWK1, CIDR1),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "ipv4_range": "%s", "name": "%s", "state": "present"}' % (CIDR1, NETWK1),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE new network, changed [success]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'name=%s ipv4_range=%s' % (NETWK2, CIDR2),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "ipv4_range": "10.240.32.0/24", "name": "%s", "state": "present"}' % (NETWK2),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE new fw rule missing params [FAIL]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'name=%s fwname=%s' % (NETWK1, FW1),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Missing required firewall rule parameter(s)"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE new fw rule bad params [FAIL]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'name=%s fwname=broken allowed=blah src_tags="one,two"' % (NETWK1),
|
||||||
|
'r': "127.0.0.1 | FAILED >> {\"changed\": false, \"failed\": true, \"msg\": \"Unexpected response: HTTP return_code[400], API error code[None] and message: Invalid value for field 'resource.allowed[0].IPProtocol': 'blah'. Must be one of [\\\"tcp\\\", \\\"udp\\\", \\\"icmp\\\"] or an IP protocol number between 0 and 255\"}",
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE existing fw rule [success]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'name=%s fwname=%s allowed="tcp:80" src_tags="one,two"' % (NETWK1, FW1),
|
||||||
|
'r': '127.0.0.1 | success >> {"allowed": "tcp:80", "changed": false, "fwname": "%s", "ipv4_range": "%s", "name": "%s", "src_range": null, "src_tags": ["one", "two"], "state": "present"}' % (FW1, CIDR1, NETWK1),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE new fw rule [success]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'name=%s fwname=%s allowed="tcp:80" src_tags="one,two"' % (NETWK1, FW3),
|
||||||
|
'r': '127.0.0.1 | success >> {"allowed": "tcp:80", "changed": true, "fwname": "%s", "ipv4_range": "%s", "name": "%s", "src_range": null, "src_tags": ["one", "two"], "state": "present"}' % (FW3, CIDR1, NETWK1),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE new network *and* fw rule [success]',
|
||||||
|
'm': 'gce_net',
|
||||||
|
'a': 'name=%s ipv4_range=%s fwname=%s allowed="tcp:80" src_tags="one,two"' % (NETWK3, CIDR3, FW4),
|
||||||
|
'r': '127.0.0.1 | success >> {"allowed": "tcp:80", "changed": true, "fwname": "%s", "ipv4_range": "%s", "name": "%s", "src_range": null, "src_tags": ["one", "two"], "state": "present"}' % (FW4, CIDR3, NETWK3),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'teardown': ['gcutil deletefirewall -f %s' % (FW1),
|
||||||
|
'gcutil deletefirewall -f %s' % (FW2),
|
||||||
|
'gcutil deletefirewall -f %s' % (FW3),
|
||||||
|
'gcutil deletefirewall -f %s' % (FW4),
|
||||||
|
'sleep 5',
|
||||||
|
'gcutil deletenetwork -f %s' % (NETWK1),
|
||||||
|
'gcutil deletenetwork -f %s' % (NETWK2),
|
||||||
|
'gcutil deletenetwork -f %s' % (NETWK3),
|
||||||
|
'sleep 5'],
|
||||||
|
},
|
||||||
|
|
||||||
|
{'id': '08', 'desc': 'Create load-balancer resources',
|
||||||
|
'setup': ['gcutil addinstance "%s" --zone=%s --machine_type=%s --network=%s --service_account_scopes="%s" --image="%s" --nopersistent_boot_disk' % (INAME, ZONE, TYPE, NETWORK, SCOPES, IMAGE),
|
||||||
|
'gcutil addinstance "%s" --wait_until_running --zone=%s --machine_type=%s --network=%s --service_account_scopes="%s" --image="%s" --nopersistent_boot_disk' % (INAME2, ZONE, TYPE, NETWORK, SCOPES, IMAGE),
|
||||||
|
],
|
||||||
|
'tests': [
|
||||||
|
{'desc': 'Do nothing [FAIL]',
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'httphealthcheck_port=7',
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Nothing to do, please specify a \\\"name\\\" or \\\"httphealthcheck_name\\\" parameter"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_HC create basic http healthcheck [success]',
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'httphealthcheck_name=%s' % (HC1),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "httphealthcheck_healthy_count": 2, "httphealthcheck_host": null, "httphealthcheck_interval": 5, "httphealthcheck_name": "%s", "httphealthcheck_path": "/", "httphealthcheck_port": 80, "httphealthcheck_timeout": 5, "httphealthcheck_unhealthy_count": 2, "name": null, "state": "present"}' % (HC1),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_HC (repeat, no-op) create basic http healthcheck [success]',
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'httphealthcheck_name=%s' % (HC1),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "httphealthcheck_healthy_count": 2, "httphealthcheck_host": null, "httphealthcheck_interval": 5, "httphealthcheck_name": "%s", "httphealthcheck_path": "/", "httphealthcheck_port": 80, "httphealthcheck_timeout": 5, "httphealthcheck_unhealthy_count": 2, "name": null, "state": "present"}' % (HC1),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_HC create custom http healthcheck [success]',
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'httphealthcheck_name=%s httphealthcheck_port=1234 httphealthcheck_path="/whatup" httphealthcheck_host="foo" httphealthcheck_interval=300' % (HC2),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "httphealthcheck_healthy_count": 2, "httphealthcheck_host": "foo", "httphealthcheck_interval": 300, "httphealthcheck_name": "%s", "httphealthcheck_path": "/whatup", "httphealthcheck_port": 1234, "httphealthcheck_timeout": 5, "httphealthcheck_unhealthy_count": 2, "name": null, "state": "present"}' % (HC2),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_HC create (broken) custom http healthcheck [FAIL]',
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'httphealthcheck_name=%s httphealthcheck_port="string" httphealthcheck_path=7' % (HC3),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Unexpected response: HTTP return_code[400], API error code[None] and message: Invalid value for: Expected a signed integer, got \'string\' (class java.lang.String)"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_LB create lb, missing region [FAIL]',
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'name=%s' % (LB1),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Missing required region name"}',
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_LB create lb, bogus region [FAIL]',
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'name=%s region=bogus' % (LB1),
|
||||||
|
'r': '127.0.0.1 | FAILED >> {"changed": false, "failed": true, "msg": "Unexpected response: HTTP return_code[404], API error code[None] and message: The resource \'projects/%s/regions/bogus\' was not found"}' % (PROJECT),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_LB create lb, minimal params [success]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'name=%s region=%s' % (LB1, REGION),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "external_ip": "173.255.123.245", "httphealthchecks": [], "members": [], "name": "%s", "port_range": "1-65535", "protocol": "tcp", "region": "%s", "state": "present"}' % (LB1, REGION),
|
||||||
|
},
|
||||||
|
{'desc': 'CREATE_LB create lb full params [success]',
|
||||||
|
'strip_numbers': True,
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'httphealthcheck_name=%s httphealthcheck_port=5055 httphealthcheck_path="/howami" name=%s port_range=8000-8888 region=%s members=%s/%s,%s/%s' % (HC3,LB2,REGION,ZONE,INAME,ZONE,INAME2),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "external_ip": "173.255.126.81", "httphealthcheck_healthy_count": 2, "httphealthcheck_host": null, "httphealthcheck_interval": 5, "httphealthcheck_name": "%s", "httphealthcheck_path": "/howami", "httphealthcheck_port": 5055, "httphealthcheck_timeout": 5, "httphealthcheck_unhealthy_count": 2, "httphealthchecks": ["%s"], "members": ["%s/%s", "%s/%s"], "name": "%s", "port_range": "8000-8888", "protocol": "tcp", "region": "%s", "state": "present"}' % (HC3,HC3,ZONE,INAME,ZONE,INAME2,LB2,REGION),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'teardown': [
|
||||||
|
'gcutil deleteinstance --zone=%s -f %s %s' % (ZONE, INAME, INAME2),
|
||||||
|
'gcutil deleteforwardingrule --region=%s -f %s %s' % (REGION, LB1, LB2),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil deletetargetpool --region=%s -f %s-tp %s-tp' % (REGION, LB1, LB2),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil deletehttphealthcheck -f %s %s %s' % (HC1, HC2, HC3),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{'id': '09', 'desc': 'Destroy load-balancer resources',
|
||||||
|
'setup': ['gcutil addhttphealthcheck %s' % (HC1),
|
||||||
|
'sleep 5',
|
||||||
|
'gcutil addhttphealthcheck %s' % (HC2),
|
||||||
|
'sleep 5',
|
||||||
|
'gcutil addtargetpool --health_checks=%s --region=%s %s-tp' % (HC1, REGION, LB1),
|
||||||
|
'sleep 5',
|
||||||
|
'gcutil addforwardingrule --target=%s-tp --region=%s %s' % (LB1, REGION, LB1),
|
||||||
|
'sleep 5',
|
||||||
|
'gcutil addtargetpool --region=%s %s-tp' % (REGION, LB2),
|
||||||
|
'sleep 5',
|
||||||
|
'gcutil addforwardingrule --target=%s-tp --region=%s %s' % (LB2, REGION, LB2),
|
||||||
|
'sleep 5',
|
||||||
|
],
|
||||||
|
'tests': [
|
||||||
|
{'desc': 'DELETE_LB: delete a non-existent LB [success]',
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'name=missing state=absent',
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "name": "missing", "state": "absent"}',
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE_LB: delete a non-existent LB+HC [success]',
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'name=missing httphealthcheck_name=alsomissing state=absent',
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": false, "httphealthcheck_name": "alsomissing", "name": "missing", "state": "absent"}',
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE_LB: destroy standalone healthcheck [success]',
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'httphealthcheck_name=%s state=absent' % (HC2),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "httphealthcheck_name": "%s", "name": null, "state": "absent"}' % (HC2),
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE_LB: destroy standalone balancer [success]',
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'name=%s state=absent' % (LB2),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "name": "%s", "state": "absent"}' % (LB2),
|
||||||
|
},
|
||||||
|
{'desc': 'DELETE_LB: destroy LB+HC [success]',
|
||||||
|
'm': 'gce_lb',
|
||||||
|
'a': 'name=%s httphealthcheck_name=%s state=absent' % (LB1, HC1),
|
||||||
|
'r': '127.0.0.1 | success >> {"changed": true, "httphealthcheck_name": "%s", "name": "%s", "state": "absent"}' % (HC1,LB1),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'teardown': [
|
||||||
|
'gcutil deleteforwardingrule --region=%s -f %s %s' % (REGION, LB1, LB2),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil deletetargetpool --region=%s -f %s-tp %s-tp' % (REGION, LB1, LB2),
|
||||||
|
'sleep 10',
|
||||||
|
'gcutil deletehttphealthcheck -f %s %s' % (HC1, HC2),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
def main(tests_to_run=[]):
|
||||||
|
for test in test_cases:
|
||||||
|
if tests_to_run and test['id'] not in tests_to_run:
|
||||||
|
continue
|
||||||
|
print "=> starting/setup '%s:%s'"% (test['id'], test['desc'])
|
||||||
|
if DEBUG: print "=debug>", test['setup']
|
||||||
|
for c in test['setup']:
|
||||||
|
(s,o) = run(c)
|
||||||
|
test_i = 1
|
||||||
|
for t in test['tests']:
|
||||||
|
if DEBUG: print "=>debug>", test_i, t['desc']
|
||||||
|
# run any test-specific setup commands
|
||||||
|
if t.has_key('setup'):
|
||||||
|
for setup in t['setup']:
|
||||||
|
(status, output) = run(setup)
|
||||||
|
|
||||||
|
# run any 'peek_before' commands
|
||||||
|
if t.has_key('peek_before') and PEEKING_ENABLED:
|
||||||
|
for setup in t['peek_before']:
|
||||||
|
(status, output) = run(setup)
|
||||||
|
|
||||||
|
# run the ansible test if 'a' exists, otherwise
|
||||||
|
# an empty 'a' directive allows test to run
|
||||||
|
# setup/teardown for a subsequent test.
|
||||||
|
if t['a']:
|
||||||
|
if DEBUG: print "=>debug>", t['m'], t['a']
|
||||||
|
acmd = "ansible all -o -m %s -a \"%s\"" % (t['m'],t['a'])
|
||||||
|
#acmd = "ANSIBLE_KEEP_REMOTE_FILES=1 ansible all -vvv -m %s -a \"%s\"" % (t['m'],t['a'])
|
||||||
|
(s,o) = run(acmd)
|
||||||
|
|
||||||
|
# check expected output
|
||||||
|
if DEBUG: print "=debug>", o.strip(), "!=", t['r']
|
||||||
|
print "=> %s.%02d '%s':" % (test['id'], test_i, t['desc']),
|
||||||
|
if t.has_key('strip_numbers'):
|
||||||
|
# strip out all numbers so we don't trip over different
|
||||||
|
# IP addresses
|
||||||
|
is_good = (o.strip().translate(None, "0123456789") == t['r'].translate(None, "0123456789"))
|
||||||
|
else:
|
||||||
|
is_good = (o.strip() == t['r'])
|
||||||
|
|
||||||
|
if is_good:
|
||||||
|
print "PASS"
|
||||||
|
else:
|
||||||
|
print "FAIL"
|
||||||
|
if VERBOSE:
|
||||||
|
print "=>", acmd
|
||||||
|
print "=> Expected:", t['r']
|
||||||
|
print "=> Got:", o.strip()
|
||||||
|
|
||||||
|
# run any 'peek_after' commands
|
||||||
|
if t.has_key('peek_after') and PEEKING_ENABLED:
|
||||||
|
for setup in t['peek_after']:
|
||||||
|
(status, output) = run(setup)
|
||||||
|
|
||||||
|
# run any test-specific teardown commands
|
||||||
|
if t.has_key('teardown'):
|
||||||
|
for td in t['teardown']:
|
||||||
|
(status, output) = run(td)
|
||||||
|
test_i += 1
|
||||||
|
|
||||||
|
print "=> completing/teardown '%s:%s'" % (test['id'], test['desc'])
|
||||||
|
if DEBUG: print "=debug>", test['teardown']
|
||||||
|
for c in test['teardown']:
|
||||||
|
(s,o) = run(c)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
tests_to_run = []
|
||||||
|
if len(sys.argv) == 2:
|
||||||
|
if sys.argv[1] in ["--help", "--list"]:
|
||||||
|
print "usage: %s [id1,id2,...,idN]" % sys.argv[0]
|
||||||
|
print " * An empty argument list will execute all tests"
|
||||||
|
print " * Do not need to specify tests in numerical order"
|
||||||
|
print " * List test categories with --list or --help"
|
||||||
|
print ""
|
||||||
|
for test in test_cases:
|
||||||
|
print "\t%s:%s" % (test['id'], test['desc'])
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
tests_to_run = sys.argv[1].split(',')
|
||||||
|
main(tests_to_run)
|
Loading…
Reference in a new issue