def delete_instance(module, name, wait, wait_timeout): if not name: module.fail_json(msg='name is required for the "rax_cdb" module') changed = False instance = find_instance(name) if not instance: module.exit_json(changed=False, action='delete') try: instance.delete() except Exception as e: module.fail_json(msg='%s' % e.message) else: changed = True if wait: pyrax.utils.wait_until(instance, 'status', 'SHUTDOWN', attempts=wait_timeout) if wait and instance.status != 'SHUTDOWN': module.fail_json(changed=changed, action='delete', cdb=rax_to_dict(instance), msg='Timeout waiting for "%s" databases instance to ' 'be deleted' % name) module.exit_json(changed=changed, action='delete', cdb=rax_to_dict(instance))
def rax_dns(module, comment, email, name, state, ttl): changed = False dns = pyrax.cloud_dns if not dns: module.fail_json(msg='Failed to instantiate client. This ' 'typically indicates an invalid region or an ' 'incorrectly capitalized region name.') if state == 'present': if not email: module.fail_json(msg='An "email" attribute is required for ' 'creating a domain') try: domain = dns.find(name=name) except pyrax.exceptions.NoUniqueMatch as e: module.fail_json(msg='%s' % e.message) except pyrax.exceptions.NotFound: try: domain = dns.create(name=name, emailAddress=email, ttl=ttl, comment=comment) changed = True except Exception as e: module.fail_json(msg='%s' % e.message) update = {} if comment != getattr(domain, 'comment', None): update['comment'] = comment if ttl != getattr(domain, 'ttl', None): update['ttl'] = ttl if email != getattr(domain, 'emailAddress', None): update['emailAddress'] = email if update: try: domain.update(**update) changed = True domain.get() except Exception as e: module.fail_json(msg='%s' % e.message) elif state == 'absent': try: domain = dns.find(name=name) except pyrax.exceptions.NotFound: domain = {} except Exception as e: module.fail_json(msg='%s' % e.message) if domain: try: domain.delete() changed = True except Exception as e: module.fail_json(msg='%s' % e.message) module.exit_json(changed=changed, domain=rax_to_dict(domain))
def save_user(module, cdb_id, name, password, databases, host): for arg, value in dict(cdb_id=cdb_id, name=name).items(): if not value: module.fail_json(msg='%s is required for the "rax_cdb_user" ' 'module' % arg) cdb = pyrax.cloud_databases try: instance = cdb.get(cdb_id) except Exception as e: module.fail_json(msg='%s' % e.message) changed = False user = find_user(instance, name) if not user: action = 'create' try: user = instance.create_user(name=name, password=password, database_names=databases, host=host) except Exception as e: module.fail_json(msg='%s' % e.message) else: changed = True else: action = 'update' if user.host != host: changed = True user.update(password=password, host=host) former_dbs = set([item.name for item in user.list_user_access()]) databases = set(databases) if databases != former_dbs: try: revoke_dbs = [db for db in former_dbs if db not in databases] user.revoke_user_access(db_names=revoke_dbs) new_dbs = [db for db in databases if db not in former_dbs] user.grant_user_access(db_names=new_dbs) except Exception as e: module.fail_json(msg='%s' % e.message) else: changed = True module.exit_json(changed=changed, action=action, user=rax_to_dict(user))
def cloud_identity(module, state, identity): instance = dict(authenticated=identity.authenticated, credentials=identity._creds_file) changed = False instance.update(rax_to_dict(identity)) instance['services'] = instance.get('services', {}).keys() if state == 'present': if not identity.authenticated: module.fail_json(msg='Credentials could not be verified!') module.exit_json(changed=changed, identity=instance)
def rax_facts(module, address, name, server_id): changed = False cs = pyrax.cloudservers if cs is None: module.fail_json(msg='Failed to instantiate client. This ' 'typically indicates an invalid region or an ' 'incorrectly capitalized region name.') ansible_facts = {} search_opts = {} if name: search_opts = dict(name='^%s$' % name) try: servers = cs.servers.list(search_opts=search_opts) except Exception as e: module.fail_json(msg='%s' % e.message) elif address: servers = [] try: for server in cs.servers.list(): for addresses in server.networks.values(): if address in addresses: servers.append(server) break except Exception as e: module.fail_json(msg='%s' % e.message) elif server_id: servers = [] try: servers.append(cs.servers.get(server_id)) except Exception as e: pass servers[:] = [server for server in servers if server.status != "DELETED"] if len(servers) > 1: module.fail_json(msg='Multiple servers found matching provided ' 'search parameters') elif len(servers) == 1: ansible_facts = rax_to_dict(servers[0], 'server') module.exit_json(changed=changed, ansible_facts=ansible_facts)
def delete_database(module, cdb_id, name): cdb = pyrax.cloud_databases try: instance = cdb.get(cdb_id) except Exception as e: module.fail_json(msg='%s' % e.message) changed = False database = find_database(instance, name) if database: try: database.delete() except Exception as e: module.fail_json(msg='%s' % e.message) else: changed = True module.exit_json(changed=changed, action='delete', database=rax_to_dict(database))
def save_database(module, cdb_id, name, character_set, collate): cdb = pyrax.cloud_databases try: instance = cdb.get(cdb_id) except Exception as e: module.fail_json(msg='%s' % e.message) changed = False database = find_database(instance, name) if not database: try: database = instance.create_database(name=name, character_set=character_set, collate=collate) except Exception as e: module.fail_json(msg='%s' % e.message) else: changed = True module.exit_json(changed=changed, action='create', database=rax_to_dict(database))
def rax_dns_record_ptr(module, data=None, comment=None, loadbalancer=None, name=None, server=None, state='present', ttl=7200): changed = False results = [] dns = pyrax.cloud_dns if not dns: module.fail_json(msg='Failed to instantiate client. This ' 'typically indicates an invalid region or an ' 'incorrectly capitalized region name.') if loadbalancer: item = rax_find_loadbalancer(module, pyrax, loadbalancer) elif server: item = rax_find_server(module, pyrax, server) if state == 'present': current = dns.list_ptr_records(item) for record in current: if record.data == data: if record.ttl != ttl or record.name != name: try: dns.update_ptr_record(item, record, name, data, ttl) changed = True except Exception as e: module.fail_json(msg='%s' % e.message) record.ttl = ttl record.name = name results.append(rax_to_dict(record)) break else: results.append(rax_to_dict(record)) break if not results: record = dict(name=name, type='PTR', data=data, ttl=ttl, comment=comment) try: results = dns.add_ptr_records(item, [record]) changed = True except Exception as e: module.fail_json(msg='%s' % e.message) module.exit_json(changed=changed, records=results) elif state == 'absent': current = dns.list_ptr_records(item) for record in current: if record.data == data: results.append(rax_to_dict(record)) break if results: try: dns.delete_ptr_records(item, data) changed = True except Exception as e: module.fail_json(msg='%s' % e.message) module.exit_json(changed=changed, records=results)
def cloud_load_balancer_ssl(module, loadbalancer, state, enabled, private_key, certificate, intermediate_certificate, secure_port, secure_traffic_only, https_redirect, wait, wait_timeout): # Validate arguments. if state == 'present': if not private_key: module.fail_json(msg="private_key must be provided.") else: private_key = private_key.strip() if not certificate: module.fail_json(msg="certificate must be provided.") else: certificate = certificate.strip() attempts = wait_timeout // 5 # Locate the load balancer. balancer = rax_find_loadbalancer(module, pyrax, loadbalancer) existing_ssl = balancer.get_ssl_termination() changed = False if state == 'present': # Apply or reconfigure SSL termination on the load balancer. ssl_attrs = dict(securePort=secure_port, privatekey=private_key, certificate=certificate, intermediateCertificate=intermediate_certificate, enabled=enabled, secureTrafficOnly=secure_traffic_only) needs_change = False if existing_ssl: for ssl_attr, value in ssl_attrs.items(): if ssl_attr == 'privatekey': # The private key is not included in get_ssl_termination's # output (as it shouldn't be). Also, if you're changing the # private key, you'll also be changing the certificate, # so we don't lose anything by not checking it. continue if value is not None and existing_ssl.get(ssl_attr) != value: # module.fail_json(msg='Unnecessary change', attr=ssl_attr, value=value, existing=existing_ssl.get(ssl_attr)) needs_change = True else: needs_change = True if needs_change: try: balancer.add_ssl_termination(**ssl_attrs) except pyrax.exceptions.PyraxException as e: module.fail_json(msg='%s' % e.message) changed = True elif state == 'absent': # Remove SSL termination if it's already configured. if existing_ssl: try: balancer.delete_ssl_termination() except pyrax.exceptions.PyraxException as e: module.fail_json(msg='%s' % e.message) changed = True if https_redirect is not None and balancer.httpsRedirect != https_redirect: if changed: # This wait is unavoidable because load balancers are immutable # while the SSL termination changes above are being applied. pyrax.utils.wait_for_build(balancer, interval=5, attempts=attempts) try: balancer.update(httpsRedirect=https_redirect) except pyrax.exceptions.PyraxException as e: module.fail_json(msg='%s' % e.message) changed = True if changed and wait: pyrax.utils.wait_for_build(balancer, interval=5, attempts=attempts) balancer.get() new_ssl_termination = balancer.get_ssl_termination() # Intentionally omit the private key from the module output, so you don't # accidentally echo it with `ansible-playbook -v` or `debug`, and the # certificate, which is just long. Convert other attributes to snake_case # and include https_redirect at the top-level. if new_ssl_termination: new_ssl = dict( enabled=new_ssl_termination['enabled'], secure_port=new_ssl_termination['securePort'], secure_traffic_only=new_ssl_termination['secureTrafficOnly']) else: new_ssl = None result = dict(changed=changed, https_redirect=balancer.httpsRedirect, ssl_termination=new_ssl, balancer=rax_to_dict(balancer, 'clb')) success = True if balancer.status == 'ERROR': result['msg'] = '%s failed to build' % balancer.id success = False elif wait and balancer.status not in ('ACTIVE', 'ERROR'): result['msg'] = 'Timeout waiting on %s' % balancer.id success = False if success: module.exit_json(**result) else: module.fail_json(**result)
def rax_asg(module, cooldown=300, disk_config=None, files=None, flavor=None, image=None, key_name=None, loadbalancers=None, meta=None, min_entities=0, max_entities=0, name=None, networks=None, server_name=None, state='present', user_data=None, config_drive=False, wait=True, wait_timeout=300): files = {} if files is None else files loadbalancers = [] if loadbalancers is None else loadbalancers meta = {} if meta is None else meta networks = [] if networks is None else networks changed = False au = pyrax.autoscale if not au: module.fail_json(msg='Failed to instantiate clients. This ' 'typically indicates an invalid region or an ' 'incorrectly capitalized region name.') if user_data: config_drive = True if user_data and os.path.isfile(user_data): try: f = open(user_data) user_data = f.read() f.close() except Exception as e: module.fail_json(msg='Failed to load %s' % user_data) if state == 'present': # Normalize and ensure all metadata values are strings if meta: for k, v in meta.items(): if isinstance(v, list): meta[k] = ','.join(['%s' % i for i in v]) elif isinstance(v, dict): meta[k] = json.dumps(v) elif not isinstance(v, string_types): meta[k] = '%s' % v if image: image = rax_find_image(module, pyrax, image) nics = [] if networks: for network in networks: nics.extend(rax_find_network(module, pyrax, network)) for nic in nics: # pyrax is currently returning net-id, but we need uuid # this check makes this forward compatible for a time when # pyrax uses uuid instead if nic.get('net-id'): nic.update(uuid=nic['net-id']) del nic['net-id'] # Handle the file contents personality = [] if files: for rpath in files.keys(): lpath = os.path.expanduser(files[rpath]) try: f = open(lpath, 'r') personality.append({'path': rpath, 'contents': f.read()}) f.close() except Exception as e: module.fail_json(msg='Failed to load %s' % lpath) lbs = [] if loadbalancers: for lb in loadbalancers: try: lb_id = int(lb.get('id')) except (ValueError, TypeError): module.fail_json(msg='Load balancer ID is not an integer: ' '%s' % lb.get('id')) try: port = int(lb.get('port')) except (ValueError, TypeError): module.fail_json(msg='Load balancer port is not an ' 'integer: %s' % lb.get('port')) if not lb_id or not port: continue lbs.append((lb_id, port)) try: sg = au.find(name=name) except pyrax.exceptions.NoUniqueMatch as e: module.fail_json(msg='%s' % e.message) except pyrax.exceptions.NotFound: try: sg = au.create(name, cooldown=cooldown, min_entities=min_entities, max_entities=max_entities, launch_config_type='launch_server', server_name=server_name, image=image, flavor=flavor, disk_config=disk_config, metadata=meta, personality=personality, networks=nics, load_balancers=lbs, key_name=key_name, config_drive=config_drive, user_data=user_data) changed = True except Exception as e: module.fail_json(msg='%s' % e.message) if not changed: # Scaling Group Updates group_args = {} if cooldown != sg.cooldown: group_args['cooldown'] = cooldown if min_entities != sg.min_entities: group_args['min_entities'] = min_entities if max_entities != sg.max_entities: group_args['max_entities'] = max_entities if group_args: changed = True sg.update(**group_args) # Launch Configuration Updates lc = sg.get_launch_config() lc_args = {} if server_name != lc.get('name'): lc_args['server_name'] = server_name if image != lc.get('image'): lc_args['image'] = image if flavor != lc.get('flavor'): lc_args['flavor'] = flavor disk_config = disk_config or 'AUTO' if ((disk_config or lc.get('disk_config')) and disk_config != lc.get('disk_config', 'AUTO')): lc_args['disk_config'] = disk_config if (meta or lc.get('meta')) and meta != lc.get('metadata'): lc_args['metadata'] = meta test_personality = [] for p in personality: test_personality.append({ 'path': p['path'], 'contents': base64.b64encode(p['contents']) }) if ((test_personality or lc.get('personality')) and test_personality != lc.get('personality')): lc_args['personality'] = personality if nics != lc.get('networks'): lc_args['networks'] = nics if lbs != lc.get('load_balancers'): # Work around for https://github.com/rackspace/pyrax/pull/393 lc_args['load_balancers'] = sg.manager._resolve_lbs(lbs) if key_name != lc.get('key_name'): lc_args['key_name'] = key_name if config_drive != lc.get('config_drive', False): lc_args['config_drive'] = config_drive if (user_data and base64.b64encode(user_data) != lc.get('user_data')): lc_args['user_data'] = user_data if lc_args: # Work around for https://github.com/rackspace/pyrax/pull/389 if 'flavor' not in lc_args: lc_args['flavor'] = lc.get('flavor') changed = True sg.update_launch_config(**lc_args) sg.get() if wait: end_time = time.time() + wait_timeout infinite = wait_timeout == 0 while infinite or time.time() < end_time: state = sg.get_state() if state["pending_capacity"] == 0: break time.sleep(5) module.exit_json(changed=changed, autoscale_group=rax_to_dict(sg)) else: try: sg = au.find(name=name) sg.delete() changed = True except pyrax.exceptions.NotFound as e: sg = {} except Exception as e: module.fail_json(msg='%s' % e.message) module.exit_json(changed=changed, autoscale_group=rax_to_dict(sg))
def cloud_block_storage(module, state, name, description, meta, size, snapshot_id, volume_type, wait, wait_timeout, image): changed = False volume = None instance = {} cbs = pyrax.cloud_blockstorage if cbs is None: module.fail_json(msg='Failed to instantiate client. This ' 'typically indicates an invalid region or an ' 'incorrectly capitalized region name.') if image: # pyrax<1.9.3 did not have support for specifying an image when # creating a volume which is required for bootable volumes if LooseVersion(pyrax.version.version) < LooseVersion('1.9.3'): module.fail_json(msg='Creating a bootable volume requires ' 'pyrax>=1.9.3') image = rax_find_image(module, pyrax, image) volume = rax_find_volume(module, pyrax, name) if state == 'present': if not volume: kwargs = dict() if image: kwargs['image'] = image try: volume = cbs.create(name, size=size, volume_type=volume_type, description=description, metadata=meta, snapshot_id=snapshot_id, **kwargs) changed = True except Exception as e: module.fail_json(msg='%s' % e.message) else: if wait: attempts = wait_timeout // 5 pyrax.utils.wait_for_build(volume, interval=5, attempts=attempts) volume.get() instance = rax_to_dict(volume) result = dict(changed=changed, volume=instance) if volume.status == 'error': result['msg'] = '%s failed to build' % volume.id elif wait and volume.status not in VOLUME_STATUS: result['msg'] = 'Timeout waiting on %s' % volume.id if 'msg' in result: module.fail_json(**result) else: module.exit_json(**result) elif state == 'absent': if volume: instance = rax_to_dict(volume) try: volume.delete() changed = True except Exception as e: module.fail_json(msg='%s' % e.message) module.exit_json(changed=changed, volume=instance)
def cloudservers(module, state=None, name=None, flavor=None, image=None, meta=None, key_name=None, files=None, wait=True, wait_timeout=300, disk_config=None, count=1, group=None, instance_ids=None, exact_count=False, networks=None, count_offset=0, auto_increment=False, extra_create_args=None, user_data=None, config_drive=False, boot_from_volume=False, boot_volume=None, boot_volume_size=None, boot_volume_terminate=False): meta = {} if meta is None else meta files = {} if files is None else files instance_ids = [] if instance_ids is None else instance_ids networks = [] if networks is None else networks extra_create_args = {} if extra_create_args is None else extra_create_args cs = pyrax.cloudservers cnw = pyrax.cloud_networks if not cnw: module.fail_json(msg='Failed to instantiate client. This ' 'typically indicates an invalid region or an ' 'incorrectly capitalized region name.') if state == 'present' or (state == 'absent' and instance_ids is None): if not boot_from_volume and not boot_volume and not image: module.fail_json(msg='image is required for the "rax" module') for arg, value in dict(name=name, flavor=flavor).items(): if not value: module.fail_json(msg='%s is required for the "rax" module' % arg) if boot_from_volume and not image and not boot_volume: module.fail_json(msg='image or boot_volume are required for the ' '"rax" with boot_from_volume') if boot_from_volume and image and not boot_volume_size: module.fail_json(msg='boot_volume_size is required for the "rax" ' 'module with boot_from_volume and image') if boot_from_volume and image and boot_volume: image = None servers = [] # Add the group meta key if group and 'group' not in meta: meta['group'] = group elif 'group' in meta and group is None: group = meta['group'] # Normalize and ensure all metadata values are strings for k, v in meta.items(): if isinstance(v, list): meta[k] = ','.join(['%s' % i for i in v]) elif isinstance(v, dict): meta[k] = json.dumps(v) elif not isinstance(v, string_types): meta[k] = '%s' % v # When using state=absent with group, the absent block won't match the # names properly. Use the exact_count functionality to decrease the count # to the desired level was_absent = False if group is not None and state == 'absent': exact_count = True state = 'present' was_absent = True if image: image = rax_find_image(module, pyrax, image) nics = [] if networks: for network in networks: nics.extend(rax_find_network(module, pyrax, network)) # act on the state if state == 'present': # Idempotent ensurance of a specific count of servers if exact_count is not False: # See if we can find servers that match our options if group is None: module.fail_json(msg='"group" must be provided when using ' '"exact_count"') if auto_increment: numbers = set() # See if the name is a printf like string, if not append # %d to the end try: name % 0 except TypeError as e: if e.message.startswith('not all'): name = '%s%%d' % name else: module.fail_json(msg=e.message) # regex pattern to match printf formatting pattern = re.sub(r'%\d*[sd]', r'(\d+)', name) for server in cs.servers.list(): # Ignore DELETED servers if server.status == 'DELETED': continue if server.metadata.get('group') == group: servers.append(server) match = re.search(pattern, server.name) if match: number = int(match.group(1)) numbers.add(number) number_range = xrange(count_offset, count_offset + count) available_numbers = list(set(number_range).difference(numbers)) else: # Not auto incrementing for server in cs.servers.list(): # Ignore DELETED servers if server.status == 'DELETED': continue if server.metadata.get('group') == group: servers.append(server) # available_numbers not needed here, we inspect auto_increment # again later # If state was absent but the count was changed, # assume we only wanted to remove that number of instances if was_absent: diff = len(servers) - count if diff < 0: count = 0 else: count = diff if len(servers) > count: # We have more servers than we need, set state='absent' # and delete the extras, this should delete the oldest state = 'absent' kept = servers[:count] del servers[:count] instance_ids = [] for server in servers: instance_ids.append(server.id) delete(module, instance_ids=instance_ids, wait=wait, wait_timeout=wait_timeout, kept=kept) elif len(servers) < count: # we have fewer servers than we need if auto_increment: # auto incrementing server numbers names = [] name_slice = count - len(servers) numbers_to_use = available_numbers[:name_slice] for number in numbers_to_use: names.append(name % number) else: # We are not auto incrementing server numbers, # create a list of 'name' that matches how many we need names = [name] * (count - len(servers)) else: # we have the right number of servers, just return info # about all of the matched servers instances = [] instance_ids = [] for server in servers: instances.append(rax_to_dict(server, 'server')) instance_ids.append(server.id) module.exit_json(changed=False, action=None, instances=instances, success=[], error=[], timeout=[], instance_ids={ 'instances': instance_ids, 'success': [], 'error': [], 'timeout': [] }) else: # not called with exact_count=True if group is not None: if auto_increment: # we are auto incrementing server numbers, but not with # exact_count numbers = set() # See if the name is a printf like string, if not append # %d to the end try: name % 0 except TypeError as e: if e.message.startswith('not all'): name = '%s%%d' % name else: module.fail_json(msg=e.message) # regex pattern to match printf formatting pattern = re.sub(r'%\d*[sd]', r'(\d+)', name) for server in cs.servers.list(): # Ignore DELETED servers if server.status == 'DELETED': continue if server.metadata.get('group') == group: servers.append(server) match = re.search(pattern, server.name) if match: number = int(match.group(1)) numbers.add(number) number_range = xrange(count_offset, count_offset + count + len(numbers)) available_numbers = list( set(number_range).difference(numbers)) names = [] numbers_to_use = available_numbers[:count] for number in numbers_to_use: names.append(name % number) else: # Not auto incrementing names = [name] * count else: # No group was specified, and not using exact_count # Perform more simplistic matching search_opts = {'name': '^%s$' % name, 'flavor': flavor} servers = [] for server in cs.servers.list(search_opts=search_opts): # Ignore DELETED servers if server.status == 'DELETED': continue if not rax_find_server_image(module, server, image, boot_volume): continue # Ignore servers with non matching metadata if server.metadata != meta: continue servers.append(server) if len(servers) >= count: # We have more servers than were requested, don't do # anything. Not running with exact_count=True, so we assume # more is OK instances = [] for server in servers: instances.append(rax_to_dict(server, 'server')) instance_ids = [i['id'] for i in instances] module.exit_json(changed=False, action=None, instances=instances, success=[], error=[], timeout=[], instance_ids={ 'instances': instance_ids, 'success': [], 'error': [], 'timeout': [] }) # We need more servers to reach out target, create names for # them, we aren't performing auto_increment here names = [name] * (count - len(servers)) block_device_mapping_v2 = [] if boot_from_volume: mapping = { 'boot_index': '0', 'delete_on_termination': boot_volume_terminate, 'destination_type': 'volume', } if image: mapping.update({ 'uuid': image, 'source_type': 'image', 'volume_size': boot_volume_size, }) image = None elif boot_volume: volume = rax_find_volume(module, pyrax, boot_volume) mapping.update({ 'uuid': pyrax.utils.get_id(volume), 'source_type': 'volume', }) block_device_mapping_v2.append(mapping) create(module, names=names, flavor=flavor, image=image, meta=meta, key_name=key_name, files=files, wait=wait, wait_timeout=wait_timeout, disk_config=disk_config, group=group, nics=nics, extra_create_args=extra_create_args, user_data=user_data, config_drive=config_drive, existing=servers, block_device_mapping_v2=block_device_mapping_v2) elif state == 'absent': if instance_ids is None: # We weren't given an explicit list of server IDs to delete # Let's match instead search_opts = {'name': '^%s$' % name, 'flavor': flavor} for server in cs.servers.list(search_opts=search_opts): # Ignore DELETED servers if server.status == 'DELETED': continue if not rax_find_server_image(module, server, image, boot_volume): continue # Ignore servers with non matching metadata if meta != server.metadata: continue servers.append(server) # Build a list of server IDs to delete instance_ids = [] for server in servers: if len(instance_ids) < count: instance_ids.append(server.id) else: break if not instance_ids: # No server IDs were matched for deletion, or no IDs were # explicitly provided, just exit and don't do anything module.exit_json(changed=False, action=None, instances=[], success=[], error=[], timeout=[], instance_ids={ 'instances': [], 'success': [], 'error': [], 'timeout': [] }) delete(module, instance_ids=instance_ids, wait=wait, wait_timeout=wait_timeout)
def delete(module, instance_ids=None, wait=True, wait_timeout=300, kept=None): instance_ids = [] if instance_ids is None else instance_ids kept = [] if kept is None else kept cs = pyrax.cloudservers changed = False instances = {} servers = [] for instance_id in instance_ids: servers.append(cs.servers.get(instance_id)) for server in servers: try: server.delete() except Exception as e: module.fail_json(msg=e.message) else: changed = True instance = rax_to_dict(server, 'server') instances[instance['id']] = instance # If requested, wait for server deletion if wait: end_time = time.time() + wait_timeout infinite = wait_timeout == 0 while infinite or time.time() < end_time: for server in servers: instance_id = server.id try: server.get() except Exception: instances[instance_id]['status'] = 'DELETED' instances[instance_id]['rax_status'] = 'DELETED' if not filter( lambda s: s['status'] not in ('', 'DELETED', 'ERROR'), instances.values()): break time.sleep(5) timeout = filter(lambda s: s['status'] not in ('', 'DELETED', 'ERROR'), instances.values()) error = filter(lambda s: s['status'] in ('ERROR'), instances.values()) success = filter(lambda s: s['status'] in ('', 'DELETED'), instances.values()) instances = [rax_to_dict(s, 'server') for s in kept] results = { 'changed': changed, 'action': 'delete', 'instances': instances, 'success': success, 'error': error, 'timeout': timeout, 'instance_ids': { 'instances': [i['id'] for i in instances], 'success': [i['id'] for i in success], 'error': [i['id'] for i in error], 'timeout': [i['id'] for i in timeout] } } if timeout: results['msg'] = 'Timeout waiting for all servers to delete' elif error: results['msg'] = 'Failed to delete all servers' if 'msg' in results: module.fail_json(**results) else: module.exit_json(**results)
def create(module, names=None, flavor=None, image=None, meta=None, key_name=None, files=None, wait=True, wait_timeout=300, disk_config=None, group=None, nics=None, extra_create_args=None, user_data=None, config_drive=False, existing=None, block_device_mapping_v2=None): names = [] if names is None else names meta = {} if meta is None else meta files = {} if files is None else files nics = [] if nics is None else nics extra_create_args = {} if extra_create_args is None else extra_create_args existing = [] if existing is None else existing block_device_mapping_v2 = [] if block_device_mapping_v2 is None else block_device_mapping_v2 cs = pyrax.cloudservers changed = False if user_data: config_drive = True if user_data and os.path.isfile(os.path.expanduser(user_data)): try: user_data = os.path.expanduser(user_data) f = open(user_data) user_data = f.read() f.close() except Exception as e: module.fail_json(msg='Failed to load %s' % user_data) # Handle the file contents for rpath in files.keys(): lpath = os.path.expanduser(files[rpath]) try: fileobj = open(lpath, 'r') files[rpath] = fileobj.read() fileobj.close() except Exception as e: module.fail_json(msg='Failed to load %s' % lpath) try: servers = [] bdmv2 = block_device_mapping_v2 for name in names: servers.append( cs.servers.create(name=name, image=image, flavor=flavor, meta=meta, key_name=key_name, files=files, nics=nics, disk_config=disk_config, config_drive=config_drive, userdata=user_data, block_device_mapping_v2=bdmv2, **extra_create_args)) except Exception as e: if e.message: msg = str(e.message) else: msg = repr(e) module.fail_json(msg=msg) else: changed = True if wait: end_time = time.time() + wait_timeout infinite = wait_timeout == 0 while infinite or time.time() < end_time: for server in servers: try: server.get() except Exception: server.status = 'ERROR' if not filter(lambda s: s.status not in FINAL_STATUSES, servers): break time.sleep(5) success = [] error = [] timeout = [] for server in servers: try: server.get() except Exception: server.status = 'ERROR' instance = rax_to_dict(server, 'server') if server.status == 'ACTIVE' or not wait: success.append(instance) elif server.status == 'ERROR': error.append(instance) elif wait: timeout.append(instance) untouched = [rax_to_dict(s, 'server') for s in existing] instances = success + untouched results = { 'changed': changed, 'action': 'create', 'instances': instances, 'success': success, 'error': error, 'timeout': timeout, 'instance_ids': { 'instances': [i['id'] for i in instances], 'success': [i['id'] for i in success], 'error': [i['id'] for i in error], 'timeout': [i['id'] for i in timeout] } } if timeout: results['msg'] = 'Timeout waiting for all servers to build' elif error: results['msg'] = 'Failed to build all servers' if 'msg' in results: module.fail_json(**results) else: module.exit_json(**results)
def save_instance(module, name, flavor, volume, cdb_type, cdb_version, wait, wait_timeout): for arg, value in dict(name=name, flavor=flavor, volume=volume, type=cdb_type, version=cdb_version ).items(): if not value: module.fail_json(msg='%s is required for the "rax_cdb"' ' module' % arg) if not (volume >= 1 and volume <= 150): module.fail_json(msg='volume is required to be between 1 and 150') cdb = pyrax.cloud_databases flavors = [] for item in cdb.list_flavors(): flavors.append(item.id) if not (flavor in flavors): module.fail_json(msg='unexisting flavor reference "%s"' % str(flavor)) changed = False instance = find_instance(name) if not instance: action = 'create' try: instance = cdb.create(name=name, flavor=flavor, volume=volume, type=cdb_type, version=cdb_version) except Exception as e: module.fail_json(msg='%s' % e.message) else: changed = True else: action = None if instance.volume.size != volume: action = 'resize' if instance.volume.size > volume: module.fail_json(changed=False, action=action, msg='The new volume size must be larger than ' 'the current volume size', cdb=rax_to_dict(instance)) instance.resize_volume(volume) changed = True if int(instance.flavor.id) != flavor: action = 'resize' pyrax.utils.wait_until(instance, 'status', 'ACTIVE', attempts=wait_timeout) instance.resize(flavor) changed = True if wait: pyrax.utils.wait_until(instance, 'status', 'ACTIVE', attempts=wait_timeout) if wait and instance.status != 'ACTIVE': module.fail_json(changed=changed, action=action, cdb=rax_to_dict(instance), msg='Timeout waiting for "%s" databases instance to ' 'be created' % name) module.exit_json(changed=changed, action=action, cdb=rax_to_dict(instance))
def cloud_block_storage_attachments(module, state, volume, server, device, wait, wait_timeout): cbs = pyrax.cloud_blockstorage cs = pyrax.cloudservers if cbs is None or cs is None: module.fail_json(msg='Failed to instantiate client. This ' 'typically indicates an invalid region or an ' 'incorrectly capitalized region name.') changed = False instance = {} volume = rax_find_volume(module, pyrax, volume) if not volume: module.fail_json(msg='No matching storage volumes were found') if state == 'present': server = rax_find_server(module, pyrax, server) if (volume.attachments and volume.attachments[0]['server_id'] == server.id): changed = False elif volume.attachments: module.fail_json(msg='Volume is attached to another server') else: try: volume.attach_to_instance(server, mountpoint=device) changed = True except Exception as e: module.fail_json(msg='%s' % e.message) volume.get() for key, value in vars(volume).items(): if (isinstance(value, NON_CALLABLES) and not key.startswith('_')): instance[key] = value result = dict(changed=changed) if volume.status == 'error': result['msg'] = '%s failed to build' % volume.id elif wait: attempts = wait_timeout // 5 pyrax.utils.wait_until(volume, 'status', 'in-use', interval=5, attempts=attempts) volume.get() result['volume'] = rax_to_dict(volume) if 'msg' in result: module.fail_json(**result) else: module.exit_json(**result) elif state == 'absent': server = rax_find_server(module, pyrax, server) if (volume.attachments and volume.attachments[0]['server_id'] == server.id): try: volume.detach() if wait: pyrax.utils.wait_until(volume, 'status', 'available', interval=3, attempts=0, verbose=False) changed = True except Exception as e: module.fail_json(msg='%s' % e.message) volume.get() changed = True elif volume.attachments: module.fail_json(msg='Volume is attached to another server') result = dict(changed=changed, volume=rax_to_dict(volume)) if volume.status == 'error': result['msg'] = '%s failed to build' % volume.id if 'msg' in result: module.fail_json(**result) else: module.exit_json(**result) module.exit_json(changed=changed, volume=instance)
def cloud_load_balancer(module, state, name, meta, algorithm, port, protocol, vip_type, timeout, wait, wait_timeout, vip_id): if int(timeout) < 30: module.fail_json(msg='"timeout" must be greater than or equal to 30') changed = False balancers = [] clb = pyrax.cloud_loadbalancers if not clb: module.fail_json(msg='Failed to instantiate client. This ' 'typically indicates an invalid region or an ' 'incorrectly capitalized region name.') balancer_list = clb.list() while balancer_list: retrieved = clb.list(marker=balancer_list.pop().id) balancer_list.extend(retrieved) if len(retrieved) < 2: break for balancer in balancer_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: virtual_ips = [clb.VirtualIP(type=vip_type, id=vip_id)] balancer = clb.create(name, metadata=metadata, port=port, algorithm=algorithm, protocol=protocol, timeout=timeout, virtual_ips=virtual_ips) changed = True except Exception as 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.items(): 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 pyrax.utils.wait_for_build(balancer, interval=5, attempts=attempts) balancer.get() instance = rax_to_dict(balancer, 'clb') 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 as e: module.fail_json(msg='%s' % e.message) instance = rax_to_dict(balancer, 'clb') 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 rax_dns_record(module, comment=None, data=None, domain=None, name=None, overwrite=True, priority=None, record_type='A', state='present', ttl=7200): """Function for manipulating record types other than PTR""" changed = False dns = pyrax.cloud_dns if not dns: module.fail_json(msg='Failed to instantiate client. This ' 'typically indicates an invalid region or an ' 'incorrectly capitalized region name.') if state == 'present': if not priority and record_type in ['MX', 'SRV']: module.fail_json(msg='A "priority" attribute is required for ' 'creating a MX or SRV record') try: domain = dns.find(name=domain) except Exception as e: module.fail_json(msg='%s' % e.message) try: if overwrite: record = domain.find_record(record_type, name=name) else: record = domain.find_record(record_type, name=name, data=data) except pyrax.exceptions.DomainRecordNotUnique as e: module.fail_json( msg='overwrite=true and there are multiple matching records') except pyrax.exceptions.DomainRecordNotFound as e: try: record_data = { 'type': record_type, 'name': name, 'data': data, 'ttl': ttl } if comment: record_data.update(dict(comment=comment)) if priority and record_type.upper() in ['MX', 'SRV']: record_data.update(dict(priority=priority)) record = domain.add_records([record_data])[0] changed = True except Exception as e: module.fail_json(msg='%s' % e.message) update = {} if comment != getattr(record, 'comment', None): update['comment'] = comment if ttl != getattr(record, 'ttl', None): update['ttl'] = ttl if priority != getattr(record, 'priority', None): update['priority'] = priority if data != getattr(record, 'data', None): update['data'] = data if update: try: record.update(**update) changed = True record.get() except Exception as e: module.fail_json(msg='%s' % e.message) elif state == 'absent': try: domain = dns.find(name=domain) except Exception as e: module.fail_json(msg='%s' % e.message) try: record = domain.find_record(record_type, name=name, data=data) except pyrax.exceptions.DomainRecordNotFound as e: record = {} except pyrax.exceptions.DomainRecordNotUnique as e: module.fail_json(msg='%s' % e.message) if record: try: record.delete() changed = True except Exception as e: module.fail_json(msg='%s' % e.message) module.exit_json(changed=changed, record=rax_to_dict(record))
def rax_asp(module, at=None, change=0, cron=None, cooldown=300, desired_capacity=0, is_percent=False, name=None, policy_type=None, scaling_group=None, state='present'): changed = False au = pyrax.autoscale if not au: module.fail_json(msg='Failed to instantiate client. This ' 'typically indicates an invalid region or an ' 'incorrectly capitalized region name.') try: UUID(scaling_group) except ValueError: try: sg = au.find(name=scaling_group) except Exception as e: module.fail_json(msg='%s' % e.message) else: try: sg = au.get(scaling_group) except Exception as e: module.fail_json(msg='%s' % e.message) if state == 'present': policies = filter(lambda p: name == p.name, sg.list_policies()) if len(policies) > 1: module.fail_json(msg='No unique policy match found by name') if at: args = dict(at=at) elif cron: args = dict(cron=cron) else: args = None if not policies: try: policy = sg.add_policy(name, policy_type=policy_type, cooldown=cooldown, change=change, is_percent=is_percent, desired_capacity=desired_capacity, args=args) changed = True except Exception as e: module.fail_json(msg='%s' % e.message) else: policy = policies[0] kwargs = {} if policy_type != policy.type: kwargs['policy_type'] = policy_type if cooldown != policy.cooldown: kwargs['cooldown'] = cooldown if hasattr(policy, 'change') and change != policy.change: kwargs['change'] = change if hasattr(policy, 'changePercent') and is_percent is False: kwargs['change'] = change kwargs['is_percent'] = False elif hasattr(policy, 'change') and is_percent is True: kwargs['change'] = change kwargs['is_percent'] = True if hasattr(policy, 'desiredCapacity') and change: kwargs['change'] = change elif ( (hasattr(policy, 'change') or hasattr(policy, 'changePercent')) and desired_capacity): kwargs['desired_capacity'] = desired_capacity if hasattr(policy, 'args') and args != policy.args: kwargs['args'] = args if kwargs: policy.update(**kwargs) changed = True policy.get() module.exit_json(changed=changed, autoscale_policy=rax_to_dict(policy)) else: try: policies = filter(lambda p: name == p.name, sg.list_policies()) if len(policies) > 1: module.fail_json(msg='No unique policy match found by name') elif not policies: policy = {} else: policy.delete() changed = True except Exception as e: module.fail_json(msg='%s' % e.message) module.exit_json(changed=changed, autoscale_policy=rax_to_dict(policy))