diff --git a/cloud/rax_clb b/cloud/rax_clb index 9b850e9c819..76564db59cb 100644 --- a/cloud/rax_clb +++ b/cloud/rax_clb @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python -tt # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify @@ -17,334 +17,319 @@ DOCUMENTATION = ''' --- module: rax_clb -short_description: add, modify and remove nodes from a Rackspace Cloud Load Balancer +short_description: create / delete a load balancer in Rackspace Public Cloud description: - - Adds, modifies and removes nodes from a Rackspace Cloud Load Balancer + - creates / deletes a Rackspace Public Cloud load balancer. version_added: "1.4" options: - address: - required: false + algorithm: description: - - IP address or domain name of the node + - algorithm for the balancer being created + choices: ['RANDOM', 'LEAST_CONNECTIONS', 'ROUND_ROBIN', 'WEIGHTED_LEAST_CONNECTIONS', 'WEIGHTED_ROUND_ROBIN'] + default: LEAST_CONNECTIONS api_key: - required: false description: - Rackspace API key (overrides C(credentials)) - condition: - required: false - choices: [ "enabled", "disabled", "draining" ] - description: - - Condition for the node, which determines its role within the load - balancer credentials: - required: false description: - File to find the Rackspace credentials in (ignored if C(api_key) and C(username) are provided) - load_balancer_id: - required: true - type: integer + default: null + aliases: ['creds_file'] + meta: description: - - Load balancer id - node_id: - required: false - type: integer + - A hash of metadata to associate with the instance + default: null + name: description: - - Node id + - Name to give the load balancer + default: null port: - required: false - type: integer description: - - Port number of the load balanced service on the node + - Port for the balancer being created + default: 80 + protocol: + description: + - 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: - required: false description: - - Region to authenticate in + - Region to create the load balancer in + default: DFW state: - required: false - default: "present" - choices: [ "present", "absent" ] description: - - Indicate desired state of the node + - Indicate desired state of the resource + choices: ['present', 'absent'] + default: present + timeout: + description: + - timeout for communication between the balancer and the node + default: 30 type: - required: false - choices: [ "primary", "secondary" ] description: - - Type of node + - type of interface for the balancer being created + choices: ['PUBLIC', 'SERVICENET'] + default: PUBLIC username: - required: false - description: - - Rackspace username (overrides C(credentials)) - virtualenv: - required: false description: - - Path to a virtualenv that should be activated before doing anything. - The virtualenv has to already exist. Useful if installing pyrax - globally is not an option. + - Rackspace username (overrides C(credentials)) wait: - required: false + description: + - wait for the balancer to be in state 'running' before returning default: "no" choices: [ "yes", "no" ] - description: - - Wait for the load balancer to become active before returning wait_timeout: - required: false - type: integer - default: 30 - description: - - How long to wait before giving up and returning an error - weight: - required: false description: - - Weight of node + - how long before wait gives up, in seconds + default: 300 requirements: [ "pyrax" ] -author: Lukasz Kawczynski +author: Christopher H. Laco, Matt Martz notes: - - "The following environment variables can be used: C(RAX_USERNAME), - C(RAX_API_KEY), C(RAX_CREDENTIALS) and C(RAX_REGION)." + - The following environment variables can be used, C(RAX_USERNAME), + 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, ...) ''' EXAMPLES = ''' -# Add a new node to the load balancer -- local_action: - module: rax_clb - load_balancer_id: 71 - address: 10.2.2.3 - port: 80 - condition: enabled - type: primary - wait: yes - credentials: /path/to/credentials - -# Drain connections from a node -- local_action: - module: rax_clb - load_balancer_id: 71 - node_id: 410 - condition: draining - wait: yes - credentials: /path/to/credentials - -# Remove a node from the load balancer -- local_action: - module: rax_clb - load_balancer_id: 71 - node_id: 410 - state: absent - wait: yes - credentials: /path/to/credentials +- name: Build a Load Balancer + gather_facts: False + hosts: local + connection: local + tasks: + - name: Load Balancer create request + local_action: + module: rax_clb + credentials: ~/.raxpub + name: my-lb + 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 +from types import NoneType -def _activate_virtualenv(path): - path = os.path.expanduser(path) - activate_this = os.path.join(path, 'bin', 'activate_this.py') - execfile(activate_this, dict(__file__=activate_this)) +try: + import pyrax +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 _get_node(lb, node_id): - """Return a node with the given `node_id`""" - for node in lb.nodes: - if node.id == node_id: - return node - return None +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 rax_clb' % arg) -def _is_primary(node): - """Return True if node is primary and enabled""" - return (node.type.lower() == 'primary' and - node.condition.lower() == 'enabled') + if int(timeout) < 30: + module.fail_json(msg='"timeout" must be greater than or equal to 30') + changed = False + balancers = [] -def _get_primary_nodes(lb): - """Return a list of primary and enabled nodes""" - nodes = [] - for node in lb.nodes: - if _is_primary(node): - nodes.append(node) - return nodes + clb = pyrax.cloud_loadbalancers + for balancer in clb.list(): + if name != balancer.name and name != balancer.id: + continue -def _node_to_dict(node): - """Return a dictionary containing node details""" - if not node: - return {} - return { - 'address': node.address, - 'condition': node.condition, - 'id': node.id, - 'port': node.port, - 'type': node.type, - 'weight': node.weight, - } + 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: + 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 + + 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 balancers: + balancer = balancers[0] + try: + balancer.delete() + changed = True + except Exception, e: + module.fail_json(msg='%s' % e.message) + + 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) + else: + instance = {} + + module.exit_json(changed=changed, balancer=instance) def main(): module = AnsibleModule( argument_spec=dict( - address=dict(), + algorithm=dict(choices=ALGORITHMS, default='LEAST_CONNECTIONS'), api_key=dict(), - condition=dict(choices=['enabled', 'disabled', 'draining']), - credentials=dict(), - load_balancer_id=dict(required=True, type='int'), - node_id=dict(type='int'), - port=dict(type='int'), + credentials=dict(aliases=['creds_file']), + meta=dict(type='dict', default={}), + name=dict(), + port=dict(type='int', default=80), + protocol=dict(choices=PROTOCOLS, default='HTTP'), region=dict(), state=dict(default='present', choices=['present', 'absent']), - type=dict(choices=['primary', 'secondary']), + timeout=dict(type='int', default=30), + type=dict(choices=['PUBLIC', 'SERVICENET'], default='PUBLIC'), username=dict(), - virtualenv=dict(), - wait=dict(default=False, type='bool'), - wait_timeout=dict(default=30, type='int'), - weight=dict(type='int'), + wait=dict(type='bool'), + wait_timeout=dict(default=300), ), - required_together=[ - ['api_key', 'username'] - ], ) - address = module.params['address'] - api_key = module.params['api_key'] - condition = (module.params['condition'] and - module.params['condition'].upper()) - credentials = module.params['credentials'] - load_balancer_id = module.params['load_balancer_id'] - node_id = module.params['node_id'] - port = module.params['port'] - region = module.params['region'] - state = module.params['state'] - typ = module.params['type'] and module.params['type'].upper() - username = module.params['username'] - virtualenv = module.params['virtualenv'] - wait = module.params['wait'] - wait_timeout = module.params['wait_timeout'] or 1 - weight = module.params['weight'] - - if virtualenv: - try: - _activate_virtualenv(virtualenv) - except IOError, e: - module.fail_json(msg='Failed to activate virtualenv %s (%s)' % ( - virtualenv, e)) + algorithm = module.params.get('algorithm') + api_key = module.params.get('api_key') + credentials = module.params.get('credentials') + meta = module.params.get('meta') + name = module.params.get('name') + port = module.params.get('port') + protocol = module.params.get('protocol') + 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: - import pyrax - except ImportError: - module.fail_json(msg='pyrax is not installed') - - 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') - region = region or os.environ.get('RAX_REGION') - - pyrax.set_setting("identity_type", "rackspace") + 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 + 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') if api_key and username: pyrax.set_credentials(username, api_key=api_key, region=region) elif credentials: credentials = os.path.expanduser(credentials) pyrax.set_credential_file(credentials, region=region) else: - module.fail_json(msg='Credentials not set') - except pyrax.exc.PyraxException, e: + raise Exception('No credentials supplied!') + except Exception, e: module.fail_json(msg='%s' % e.message) - if not pyrax.cloud_loadbalancers: - module.fail_json(msg='Failed to instantiate load balancer client ' - '(possibly incorrect region)') - - try: - lb = pyrax.cloud_loadbalancers.get(load_balancer_id) - except pyrax.exc.PyraxException, e: - module.fail_json(msg='%s' % e.message) - - if node_id: - node = _get_node(lb, node_id) - else: - node = None - - result = _node_to_dict(node) - - if state == 'absent': - if not node: # Removing a non-existent node - module.exit_json(changed=False, state=state) - - # The API detects this as well but currently pyrax does not return a - # meaningful error message - if _is_primary(node) and len(_get_primary_nodes(lb)) == 1: - module.fail_json( - msg='At least one primary node has to be enabled') - - try: - lb.delete_node(node) - result = {} - except pyrax.exc.NotFound: - module.exit_json(changed=False, state=state) - except pyrax.exc.PyraxException, e: - module.fail_json(msg='%s' % e.message) - else: # present - if not node: - if node_id: # Updating a non-existent node - msg = 'Node %d not found' % node_id - if lb.nodes: - msg += (' (available nodes: %s)' % - ', '.join([str(x.id) for x in lb.nodes])) - module.fail_json(msg=msg) - else: # Creating a new node - try: - node = pyrax.cloudloadbalancers.Node( - address=address, port=port, condition=condition, - weight=weight, type=typ) - resp, body = lb.add_nodes([node]) - result.update(body['nodes'][0]) - except pyrax.exc.PyraxException, e: - module.fail_json(msg='%s' % e.message) - else: # Updating an existing node - immutable = { - 'address': address, - 'port': port, - } - - mutable = { - 'condition': condition, - 'type': typ, - 'weight': weight, - } - - for name, value in immutable.items(): - if value: - module.fail_json( - msg='Attribute %s cannot be modified' % name) - - for name, value in mutable.items(): - if value is None or value == getattr(node, name): - mutable.pop(name) - - if not mutable: - module.exit_json(changed=False, state=state, node=result) - - try: - # The diff has to be set explicitly to update node's weight and - # type; this should probably be fixed in pyrax - lb.update_node(node, diff=mutable) - result.update(mutable) - except pyrax.exc.PyraxException, e: - module.fail_json(msg='%s' % e.message) - - if wait: - pyrax.utils.wait_until(lb, "status", "ACTIVE", interval=1, - attempts=wait_timeout) - if lb.status != 'ACTIVE': - module.fail_json( - msg='Load balancer not active after %ds (current status: %s)' % - (wait_timeout, lb.status.lower())) + cloud_load_balancer(module, state, name, meta, algorithm, port, protocol, + vip_type, timeout, wait, wait_timeout) - kwargs = {'node': result} if result else {} - module.exit_json(changed=True, state=state, **kwargs) +from ansible.module_utils.basic import * -# this is magic, see lib/ansible/module_common.py -#<> main() diff --git a/cloud/rax_clb_nodes b/cloud/rax_clb_nodes new file mode 100644 index 00000000000..c26206c5282 --- /dev/null +++ b/cloud/rax_clb_nodes @@ -0,0 +1,350 @@ +#!/usr/bin/python +# 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 . + +DOCUMENTATION = ''' +--- +module: rax_clb_nodes +short_description: add, modify and remove nodes from a Rackspace Cloud Load Balancer +description: + - Adds, modifies and removes nodes from a Rackspace Cloud Load Balancer +version_added: "1.4" +options: + address: + required: false + description: + - IP address or domain name of the node + api_key: + required: false + description: + - Rackspace API key (overrides C(credentials)) + condition: + required: false + choices: [ "enabled", "disabled", "draining" ] + description: + - Condition for the node, which determines its role within the load + balancer + credentials: + required: false + description: + - File to find the Rackspace credentials in (ignored if C(api_key) and + C(username) are provided) + load_balancer_id: + required: true + type: integer + description: + - Load balancer id + node_id: + required: false + type: integer + description: + - Node id + port: + required: false + type: integer + description: + - Port number of the load balanced service on the node + region: + required: false + description: + - Region to authenticate in + state: + required: false + default: "present" + choices: [ "present", "absent" ] + description: + - Indicate desired state of the node + type: + required: false + choices: [ "primary", "secondary" ] + description: + - Type of node + username: + required: false + description: + - Rackspace username (overrides C(credentials)) + virtualenv: + required: false + description: + - Path to a virtualenv that should be activated before doing anything. + The virtualenv has to already exist. Useful if installing pyrax + globally is not an option. + wait: + required: false + default: "no" + choices: [ "yes", "no" ] + description: + - Wait for the load balancer to become active before returning + wait_timeout: + required: false + type: integer + default: 30 + description: + - How long to wait before giving up and returning an error + weight: + required: false + description: + - Weight of node +requirements: [ "pyrax" ] +author: Lukasz Kawczynski +notes: + - "The following environment variables can be used: C(RAX_USERNAME), + C(RAX_API_KEY), C(RAX_CREDENTIALS) and C(RAX_REGION)." +''' + +EXAMPLES = ''' +# Add a new node to the load balancer +- local_action: + module: rax_clb_nodes + load_balancer_id: 71 + address: 10.2.2.3 + port: 80 + condition: enabled + type: primary + wait: yes + credentials: /path/to/credentials + +# Drain connections from a node +- local_action: + module: rax_clb_nodes + load_balancer_id: 71 + node_id: 410 + condition: draining + wait: yes + credentials: /path/to/credentials + +# Remove a node from the load balancer +- local_action: + module: rax_clb_nodes + load_balancer_id: 71 + node_id: 410 + state: absent + wait: yes + credentials: /path/to/credentials +''' + +import os + + +def _activate_virtualenv(path): + path = os.path.expanduser(path) + activate_this = os.path.join(path, 'bin', 'activate_this.py') + execfile(activate_this, dict(__file__=activate_this)) + + +def _get_node(lb, node_id): + """Return a node with the given `node_id`""" + for node in lb.nodes: + if node.id == node_id: + return node + return None + + +def _is_primary(node): + """Return True if node is primary and enabled""" + return (node.type.lower() == 'primary' and + node.condition.lower() == 'enabled') + + +def _get_primary_nodes(lb): + """Return a list of primary and enabled nodes""" + nodes = [] + for node in lb.nodes: + if _is_primary(node): + nodes.append(node) + return nodes + + +def _node_to_dict(node): + """Return a dictionary containing node details""" + if not node: + return {} + return { + 'address': node.address, + 'condition': node.condition, + 'id': node.id, + 'port': node.port, + 'type': node.type, + 'weight': node.weight, + } + + +def main(): + module = AnsibleModule( + argument_spec=dict( + address=dict(), + api_key=dict(), + condition=dict(choices=['enabled', 'disabled', 'draining']), + credentials=dict(), + load_balancer_id=dict(required=True, type='int'), + node_id=dict(type='int'), + port=dict(type='int'), + region=dict(), + state=dict(default='present', choices=['present', 'absent']), + type=dict(choices=['primary', 'secondary']), + username=dict(), + virtualenv=dict(), + wait=dict(default=False, type='bool'), + wait_timeout=dict(default=30, type='int'), + weight=dict(type='int'), + ), + required_together=[ + ['api_key', 'username'] + ], + ) + + address = module.params['address'] + api_key = module.params['api_key'] + condition = (module.params['condition'] and + module.params['condition'].upper()) + credentials = module.params['credentials'] + load_balancer_id = module.params['load_balancer_id'] + node_id = module.params['node_id'] + port = module.params['port'] + region = module.params['region'] + state = module.params['state'] + typ = module.params['type'] and module.params['type'].upper() + username = module.params['username'] + virtualenv = module.params['virtualenv'] + wait = module.params['wait'] + wait_timeout = module.params['wait_timeout'] or 1 + weight = module.params['weight'] + + if virtualenv: + try: + _activate_virtualenv(virtualenv) + except IOError, e: + module.fail_json(msg='Failed to activate virtualenv %s (%s)' % ( + virtualenv, e)) + + try: + import pyrax + except ImportError: + module.fail_json(msg='pyrax is not installed') + + 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') + region = region or os.environ.get('RAX_REGION') + + pyrax.set_setting("identity_type", "rackspace") + + try: + if api_key and username: + pyrax.set_credentials(username, api_key=api_key, region=region) + elif credentials: + credentials = os.path.expanduser(credentials) + pyrax.set_credential_file(credentials, region=region) + else: + module.fail_json(msg='Credentials not set') + except pyrax.exc.PyraxException, e: + module.fail_json(msg='%s' % e.message) + + if not pyrax.cloud_loadbalancers: + module.fail_json(msg='Failed to instantiate load balancer client ' + '(possibly incorrect region)') + + try: + lb = pyrax.cloud_loadbalancers.get(load_balancer_id) + except pyrax.exc.PyraxException, e: + module.fail_json(msg='%s' % e.message) + + if node_id: + node = _get_node(lb, node_id) + else: + node = None + + result = _node_to_dict(node) + + if state == 'absent': + if not node: # Removing a non-existent node + module.exit_json(changed=False, state=state) + + # The API detects this as well but currently pyrax does not return a + # meaningful error message + if _is_primary(node) and len(_get_primary_nodes(lb)) == 1: + module.fail_json( + msg='At least one primary node has to be enabled') + + try: + lb.delete_node(node) + result = {} + except pyrax.exc.NotFound: + module.exit_json(changed=False, state=state) + except pyrax.exc.PyraxException, e: + module.fail_json(msg='%s' % e.message) + else: # present + if not node: + if node_id: # Updating a non-existent node + msg = 'Node %d not found' % node_id + if lb.nodes: + msg += (' (available nodes: %s)' % + ', '.join([str(x.id) for x in lb.nodes])) + module.fail_json(msg=msg) + else: # Creating a new node + try: + node = pyrax.cloudloadbalancers.Node( + address=address, port=port, condition=condition, + weight=weight, type=typ) + resp, body = lb.add_nodes([node]) + result.update(body['nodes'][0]) + except pyrax.exc.PyraxException, e: + module.fail_json(msg='%s' % e.message) + else: # Updating an existing node + immutable = { + 'address': address, + 'port': port, + } + + mutable = { + 'condition': condition, + 'type': typ, + 'weight': weight, + } + + for name, value in immutable.items(): + if value: + module.fail_json( + msg='Attribute %s cannot be modified' % name) + + for name, value in mutable.items(): + if value is None or value == getattr(node, name): + mutable.pop(name) + + if not mutable: + module.exit_json(changed=False, state=state, node=result) + + try: + # The diff has to be set explicitly to update node's weight and + # type; this should probably be fixed in pyrax + lb.update_node(node, diff=mutable) + result.update(mutable) + except pyrax.exc.PyraxException, e: + module.fail_json(msg='%s' % e.message) + + if wait: + pyrax.utils.wait_until(lb, "status", "ACTIVE", interval=1, + attempts=wait_timeout) + if lb.status != 'ACTIVE': + module.fail_json( + msg='Load balancer not active after %ds (current status: %s)' % + (wait_timeout, lb.status.lower())) + + kwargs = {'node': result} if result else {} + module.exit_json(changed=True, state=state, **kwargs) + +# this is magic, see lib/ansible/module_common.py +#<> +main()