diff --git a/cloud/rax_clb b/cloud/rax_clb index 498ab1414ef..404f6891407 100644 --- a/cloud/rax_clb +++ b/cloud/rax_clb @@ -22,70 +22,74 @@ description: - creates / deletes a Rackspace Public Cloud load balancer. version_added: "1.4" options: - state: + algorithm: description: - - Indicate desired state of the resource - choices: ['present', 'absent'] - default: present + - algorithm for the balancer being created + choices: ['RANDOM', 'LEAST_CONNECTIONS', 'ROUND_ROBIN', 'WEIGHTED_LEAST_CONNECTIONS', 'WEIGHTED_ROUND_ROBIN'] + default: LEAST_CONNECTIONS + api_key: + description: + - Rackspace API key (overrides C(credentials)) credentials: description: - - File to find the Rackspace credentials in (ignored if C(api_key) and - C(username) are provided) + - File to find the Rackspace credentials in (ignored if C(api_key) and + C(username) are provided) default: null aliases: ['creds_file'] - api_key: + meta: description: - - Rackspace API key (overrides C(credentials)) - username: - description: - - Rackspace username (overrides C(credentials)) + - A hash of metadata to associate with the instance + default: null name: description: - - Name to give the load balancer + - Name to give the load balancer default: null - algorithm: - description: - - algorithm for the balancer being created - choices: ['RANDOM', 'LEAST_CONNECTIONS', 'ROUND_ROBIN', 'WEIGHTED_LEAST_CONNECTIONS', 'WEIGHTED_ROUND_ROBIN'] - default: LEAST_CONNECTIONS port: description: - - Port for the balancer being created + - Port for the balancer being created default: 80 protocol: description: - - Protocol for the balancer being created + - Protocol for the balancer being created choices: ['DNS_TCP', 'DNS_UDP' ,'FTP', 'HTTP', 'HTTPS', 'IMAPS', 'IMAPv4', 'LDAP', 'LDAPS', 'MYSQL', 'POP3', 'POP3S', 'SMTP', 'TCP', 'TCP_CLIENT_FIRST', 'UDP', 'UDP_STREAM', 'SFTP'] default: HTTP + region: + description: + - Region to create the load balancer in + default: DFW + state: + description: + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present timeout: description: - - timeout for communication between the balancer and the node + - timeout for communication between the balancer and the node default: 30 type: description: - - type of interface for the balancer being created + - type of interface for the balancer being created choices: ['PUBLIC', 'SERVICENET'] default: PUBLIC - region: + username: description: - - Region to create the load balancer in - default: DFW + - Rackspace username (overrides C(credentials)) wait: description: - - wait for the balancer to be in state 'running' before returning + - wait for the balancer to be in state 'running' before returning default: "no" choices: [ "yes", "no" ] wait_timeout: description: - - how long before wait gives up, in seconds + - how long before wait gives up, in seconds default: 300 requirements: [ "pyrax" ] -author: Christopher H. Laco, Jesse Keating +author: Christopher H. Laco, Matt Martz notes: - The following environment variables can be used, C(RAX_USERNAME), - C(RAX_API_KEY), C(RAX_CREDS), C(RAX_CREDENTIALS), C(RAX_REGION). - - C(RAX_CREDENTIALS) and C(RAX_CREDS) points to a credentials file - appropriate for pyrax + C(RAX_API_KEY), C(RAX_CREDS_FILE), C(RAX_CREDENTIALS), C(RAX_REGION). + - C(RAX_CREDENTIALS) and C(RAX_CREDS_FILE) points to a credentials file + appropriate for pyrax. See U(https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md#authenticating) - C(RAX_USERNAME) and C(RAX_API_KEY) obviate the use of a credentials file - C(RAX_REGION) defines a Rackspace Public Cloud region (DFW, ORD, LON, ...) ''' @@ -93,169 +97,224 @@ notes: EXAMPLES = ''' - name: Build a Load Balancer gather_facts: False - + hosts: local + connection: local tasks: - - name: Provision Load Balancer - rax_lb: + - name: Load Balancer create request + local_action: + module: rax_clb credentials: ~/.raxpub name: my-lb - port: 9000 + port: 8080 protocol: HTTP type: SERVICENET timeout: 30 region: DFW wait: yes state: present + meta: + app: my-cool-app register: my_lb ''' import sys import os -import time + +from types import NoneType try: import pyrax - import pyrax.utils - from pyrax import exc except ImportError: print("failed=True msg='pyrax required for this module'") sys.exit(1) +NON_CALLABLES = (basestring, bool, dict, int, list, NoneType) +ALGORITHMS = ['RANDOM', 'LEAST_CONNECTIONS', 'ROUND_ROBIN', + 'WEIGHTED_LEAST_CONNECTIONS', 'WEIGHTED_ROUND_ROBIN'] +PROTOCOLS = ['DNS_TCP', 'DNS_UDP', 'FTP', 'HTTP', 'HTTPS', 'IMAPS', 'IMAPv4', + 'LDAP', 'LDAPS', 'MYSQL', 'POP3', 'POP3S', 'SMTP', 'TCP', + 'TCP_CLIENT_FIRST', 'UDP', 'UDP_STREAM', 'SFTP'] + -def cloud_load_balancer(module, state, name, meta, algorithm, port, protocol, type, timeout, - wait, wait_timeout): - for arg in (state, name, port, protocol, type): +def cloud_load_balancer(module, state, name, meta, algorithm, port, protocol, + vip_type, timeout, wait, wait_timeout): + for arg in (state, name, port, protocol, vip_type): if not arg: - module.fail_json(msg='%s is required for cloud_loadbalancers' % arg) + module.fail_json(msg='%s is required for rax_clb' % arg) + + if int(timeout) < 30: + module.fail_json(msg='"timeout" must be greater than or equal to 30') changed = False - balancer = None balancers = [] - instances = [] - for balancer in pyrax.cloud_loadbalancers.list(): - if name != balancer.name: + clb = pyrax.cloud_loadbalancers + + for balancer in clb.list(): + if name != balancer.name and name != balancer.id: continue balancers.append(balancer) + if len(balancers) > 1: + module.fail_json(msg='Multiple Load Balancers were matched by name, ' + 'try using the Load Balancer ID instead') + if state == 'present': + if isinstance(meta, dict): + metadata = [dict(key=k, value=v) for k, v in meta.items()] + if not balancers: try: - balancers=[pyrax.cloud_loadbalancers.create( - name, - metadata=meta, - algorithm=algorithm, - port=port, - protocol=protocol, - timeout=timeout, - virtual_ips=[pyrax.cloud_loadbalancers.VirtualIP(type=type)] - )] + virtual_ips = [clb.VirtualIP(type=vip_type)] + balancer = clb.create(name, metadata=metadata, port=port, + algorithm=algorithm, protocol=protocol, + timeout=timeout, virtual_ips=virtual_ips) changed = True except Exception, e: module.fail_json(msg='%s' % e.message) + else: + balancer = balancers[0] + setattr(balancer, 'metadata', + [dict(key=k, value=v) for k, v in + balancer.get_metadata().items()]) + atts = { + 'name': name, + 'algorithm': algorithm, + 'port': port, + 'protocol': protocol, + 'timeout': timeout + } + for att, value in atts.iteritems(): + current = getattr(balancer, att) + if current != value: + changed = True - for balancer in balancers: - if wait: - pyrax.utils.wait_for_build(balancer, interval=5, attempts=5) - - if balancer.status == 'ACTIVE': - instance = dict( - id=balancer.id, - algorithm=balancer.algorithm, - cluster=balancer.cluster, - connectionLogging=balancer.connectionLogging, - contentCaching=balancer.contentCaching, - halfClosed=balancer.halfClosed, - port=balancer.port, - protocol=balancer.protocol, - sourceAddresses=balancer.sourceAddresses, - name=balancer.name, - timeout=balancer.timeout, - status=balancer.status - ) - instances.append(instance) - elif balancer.status == 'ERROR': - module.fail_json(msg='%s failed to build' % balancer.id) - elif wait: - module.fail_json(msg='Timeout waiting on %s' % balancer.id) + if changed: + balancer.update(**atts) + + if balancer.metadata != metadata: + balancer.set_metadata(meta) + changed = True + + virtual_ips = [clb.VirtualIP(type=vip_type)] + current_vip_types = set([v.type for v in balancer.virtual_ips]) + vip_types = set([v.type for v in virtual_ips]) + if current_vip_types != vip_types: + module.fail_json(msg='Load balancer Virtual IP type cannot ' + 'be changed') + + if wait: + attempts = wait_timeout / 5 + print attempts + pyrax.utils.wait_for_build(balancer, interval=5, attempts=attempts) + + balancer.get() + instance = {} + for key, value in vars(balancer).iteritems(): + if key == 'virtual_ips': + virtual_ips = [] + instance[key] = [] + for vip in value: + vip_dict = {} + for vip_key, vip_value in vars(vip).iteritems(): + if isinstance(vip_value, NON_CALLABLES): + vip_dict[vip_key] = vip_value + instance[key].append(vip_dict) + elif (isinstance(value, NON_CALLABLES) and + not key.startswith('_')): + instance[key] = value + + result = dict(changed=changed, balancer=instance) + + if balancer.status == 'ERROR': + result['msg'] = '%s failed to build' % balancer.id + elif wait and balancer.status not in ('ACTIVE', 'ERROR'): + result['msg'] = 'Timeout waiting on %s' % balancer.id + + if 'msg' in result: + module.fail_json(**result) + else: + module.exit_json(**result) elif state == 'absent': - if balancer: + if balancers: + balancer = balancers[0] try: balancer.delete() changed = True except Exception, e: module.fail_json(msg='%s' % e.message) - instance = dict( - id=balancer.id, - algorithm=balancer.algorithm, - cluster=None, - connectionLogging=None, - contentCaching=None, - halfClosed=None, - port=balancer.port, - protocol=balancer.protocol, - sourceAddresses=None, - name=balancer.name, - timeout=balancer.timeout, - status=balancer.status - ) - instances.append(instance) - - module.exit_json(changed=changed, balancers=instances) + instance = {} + for key, value in vars(balancer).iteritems(): + if key == 'virtual_ips': + virtual_ips = [] + instance[key] = [] + for vip in value: + vip_dict = {} + for vip_key, vip_value in vars(vip).iteritems(): + if isinstance(vip_value, NON_CALLABLES): + vip_dict[vip_key] = vip_value + instance[key].append(vip_dict) + elif (isinstance(value, NON_CALLABLES) and + not key.startswith('_')): + instance[key] = value + + if wait: + attempts = wait_timeout / 5 + pyrax.utils.wait_until(balancer, 'status', ('DELETED'), + interval=5, attempts=attempts) + + module.exit_json(changed=changed, balancer=instance) def main(): module = AnsibleModule( argument_spec=dict( - state=dict(default='present', - choices=['present', 'absent']), - credentials=dict(aliases=['creds_file']), + algorithm=dict(choices=ALGORITHMS, default='LEAST_CONNECTIONS'), api_key=dict(), - username=dict(), - region=dict(), - name=dict(), + credentials=dict(aliases=['creds_file']), meta=dict(type='dict', default={}), - algorithm=dict(choices=['RANDOM', 'LEAST_CONNECTIONS', 'ROUND_ROBIN', 'WEIGHTED_LEAST_CONNECTIONS', 'WEIGHTED_ROUND_ROBIN'], default='LEAST_CONNECTIONS'), + name=dict(), port=dict(type='int', default=80), - protocol=dict(choices=['DNS_TCP', 'DNS_UDP' ,'FTP', 'HTTP', 'HTTPS', 'IMAPS', 'IMAPv4', 'LDAP', 'LDAPS', 'MYSQL', 'POP3', 'POP3S', 'SMTP', 'TCP', 'TCP_CLIENT_FIRST', 'UDP', 'UDP_STREAM', 'SFTP'], default='HTTP'), - type=dict(choices=['PUBLIC', 'SERVICENET'], default='PUBLIC'), + protocol=dict(choices=PROTOCOLS, default='HTTP'), + region=dict(), + state=dict(default='present', choices=['present', 'absent']), timeout=dict(type='int', default=30), + type=dict(choices=['PUBLIC', 'SERVICENET'], default='PUBLIC'), + username=dict(), wait=dict(type='bool'), - wait_timeout=dict(default=300) - ) + wait_timeout=dict(default=300), + ), ) - credentials = module.params.get('credentials') + algorithm = module.params.get('algorithm') api_key = module.params.get('api_key') - username = module.params.get('username') - region = module.params.get('region') - state = module.params.get('state') - name = module.params.get('name') + credentials = module.params.get('credentials') meta = module.params.get('meta') - algorithm = module.params.get('algorithm') + name = module.params.get('name') port = module.params.get('port') protocol = module.params.get('protocol') - type = module.params.get('type') + region = module.params.get('region') + state = module.params.get('state') timeout = int(module.params.get('timeout')) + username = module.params.get('username') + vip_type = module.params.get('type') wait = module.params.get('wait') wait_timeout = int(module.params.get('wait_timeout')) try: username = username or os.environ.get('RAX_USERNAME') api_key = api_key or os.environ.get('RAX_API_KEY') - credentials = (credentials or - os.environ.get('RAX_CREDENTIALS') or + credentials = (credentials or os.environ.get('RAX_CREDENTIALS') or os.environ.get('RAX_CREDS_FILE')) region = region or os.environ.get('RAX_REGION') - except KeyError, e: module.fail_json(msg='Unable to load %s' % e.message) try: - pyrax.set_setting("identity_type", "rackspace") + pyrax.set_setting('identity_type', 'rackspace') if api_key and username: pyrax.set_credentials(username, api_key=api_key, region=region) elif credentials: @@ -266,9 +325,9 @@ def main(): except Exception, e: module.fail_json(msg='%s' % e.message) - cloud_load_balancer(module, state, name, meta, algorithm, port, protocol, type, timeout, wait, wait_timeout) + cloud_load_balancer(module, state, name, meta, algorithm, port, protocol, + vip_type, timeout, wait, wait_timeout) -# this is magic, see lib/ansible/module_common.py -#<> +from ansible.module_utils.basic import * main()