2013-10-07 23:01:37 +02:00
|
|
|
#!/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
|
|
|
|
|
2013-10-08 18:36:35 +02:00
|
|
|
USER_AGENT_PRODUCT="Ansible-gce_lb"
|
|
|
|
USER_AGENT_VERSION="v1beta15"
|
|
|
|
|
2013-10-07 23:01:37 +02:00
|
|
|
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)
|
2013-10-08 18:36:35 +02:00
|
|
|
gce.connection.user_agent_append("%s/%s" % (
|
|
|
|
USER_AGENT_PRODUCT, USER_AGENT_VERSION))
|
2013-10-07 23:01:37 +02:00
|
|
|
gcelb = get_driver_lb(Provider_lb.GCE)(gce_driver=gce)
|
2013-10-08 18:36:35 +02:00
|
|
|
gcelb.connection.user_agent_append("%s/%s" % (
|
|
|
|
USER_AGENT_PRODUCT, USER_AGENT_VERSION))
|
2013-10-07 23:01:37 +02:00
|
|
|
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()
|