def destroy(name, call=None): ''' Destroy a node. Will check termination protection and warn if enabled. CLI Example: .. code-block:: bash salt-cloud --destroy mymachine ''' if call == 'function': raise SaltCloudSystemExit( 'The destroy action must be called with -d, --destroy, ' '-a or --action.') salt.utils.cloud.fire_event('event', 'destroying instance', 'salt/cloud/{0}/destroying'.format(name), args={'name': name}, sock_dir=__opts__['sock_dir'], transport=__opts__['transport']) data = show_instance(name, call='action') node = query(method='droplets', droplet_id=data['id'], http_method='delete') ## This is all terribly optomistic: # vm_ = get_vm_config(name=name) # delete_dns_record = config.get_cloud_config_value( # 'delete_dns_record', vm_, __opts__, search_global=False, default=None, # ) # TODO: when _vm config data can be made available, we should honor the configuration settings, # but until then, we should assume stale DNS records are bad, and default behavior should be to # delete them if we can. When this is resolved, also resolve the comments a couple of lines below. delete_dns_record = True if not isinstance(delete_dns_record, bool): raise SaltCloudConfigError( '\'delete_dns_record\' should be a boolean value.') # When the "to do" a few lines up is resolved, remove these lines and use the if/else logic below. log.debug('Deleting DNS records for {0}.'.format(name)) destroy_dns_records(name) # Until the "to do" from line 754 is taken care of, we don't need this logic. # if delete_dns_record: # log.debug('Deleting DNS records for {0}.'.format(name)) # destroy_dns_records(name) # else: # log.debug('delete_dns_record : {0}'.format(delete_dns_record)) # for line in pprint.pformat(dir()).splitlines(): # log.debug('delete context: {0}'.format(line)) salt.utils.cloud.fire_event('event', 'destroyed instance', 'salt/cloud/{0}/destroyed'.format(name), args={'name': name}, sock_dir=__opts__['sock_dir'], transport=__opts__['transport']) if __opts__.get('update_cachedir', False) is True: salt.utils.cloud.delete_minion_cachedir( name, __active_provider_name__.split(':')[0], __opts__) return node
def create(vm_): """ Create a single VM from a data dict """ try: # Check for required profile parameters before sending any API calls. if (vm_["profile"] and config.is_profile_configured( __opts__, __active_provider_name__ or "softlayer_hw", vm_["profile"], vm_=vm_, ) is False): return False except AttributeError: pass name = vm_["name"] hostname = name domain = config.get_cloud_config_value("domain", vm_, __opts__, default=None) if domain is None: SaltCloudSystemExit( "A domain name is required for the SoftLayer driver.") if vm_.get("use_fqdn"): name = ".".join([name, domain]) vm_["name"] = name __utils__["cloud.fire_event"]( "event", "starting create", "salt/cloud/{0}/creating".format(name), args=__utils__["cloud.filter_event"]( "creating", vm_, ["name", "profile", "provider", "driver"]), sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) log.info("Creating Cloud VM %s", name) conn = get_conn(service="SoftLayer_Product_Order") kwargs = { "complexType": "SoftLayer_Container_Product_Order_Hardware_Server", "quantity": 1, "hardware": [{ "hostname": hostname, "domain": domain }], # Baremetal Package "packageId": 50, "prices": [ # Size Ex: 1921: 2 x 2.0 GHz Core Bare Metal Instance - 2 GB Ram { "id": vm_["size"] }, # HDD Ex: 19: 250GB SATA II { "id": vm_["hdd"] }, # Image Ex: 13963: CentOS 6.0 - Minimal Install (64 bit) { "id": vm_["image"] }, # The following items are currently required # Reboot / Remote Console { "id": "905" }, # 1 IP Address { "id": "21" }, # Host Ping Monitoring { "id": "55" }, # Email and Ticket Notifications { "id": "57" }, # Automated Notification Response { "id": "58" }, # Unlimited SSL VPN Users & 1 PPTP VPN User per account { "id": "420" }, # Nessus Vulnerability Assessment & Reporting { "id": "418" }, ], } optional_products = config.get_cloud_config_value("optional_products", vm_, __opts__, default=[]) for product in optional_products: kwargs["prices"].append({"id": product}) # Default is 273 (100 Mbps Public & Private Networks) port_speed = config.get_cloud_config_value("port_speed", vm_, __opts__, default=273) kwargs["prices"].append({"id": port_speed}) # Default is 1800 (0 GB Bandwidth) bandwidth = config.get_cloud_config_value("bandwidth", vm_, __opts__, default=1800) kwargs["prices"].append({"id": bandwidth}) post_uri = config.get_cloud_config_value("post_uri", vm_, __opts__, default=None) if post_uri: kwargs["prices"].append({"id": post_uri}) vlan_id = config.get_cloud_config_value("vlan", vm_, __opts__, default=False) if vlan_id: kwargs["primaryNetworkComponent"] = {"networkVlan": {"id": vlan_id}} location = get_location(vm_) if location: kwargs["location"] = location __utils__["cloud.fire_event"]( "event", "requesting instance", "salt/cloud/{0}/requesting".format(name), args={ "kwargs": __utils__["cloud.filter_event"]("requesting", kwargs, list(kwargs)), }, sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) try: response = conn.placeOrder(kwargs) # Leaving the following line in, commented, for easy debugging # response = conn.verifyOrder(kwargs) except Exception as exc: # pylint: disable=broad-except log.error( "Error creating %s on SoftLayer\n\n" "The following exception was thrown when trying to " "run the initial deployment: \n%s", name, exc, # Show the traceback if the debug logging level is enabled exc_info_on_loglevel=logging.DEBUG, ) return False def wait_for_ip(): """ Wait for the IP address to become available """ nodes = list_nodes_full() if "primaryIpAddress" in nodes[hostname]: return nodes[hostname]["primaryIpAddress"] time.sleep(1) return False ip_address = salt.utils.cloud.wait_for_fun( wait_for_ip, timeout=config.get_cloud_config_value("wait_for_fun_timeout", vm_, __opts__, default=15 * 60), ) ssh_connect_timeout = config.get_cloud_config_value( # 15 minutes "ssh_connect_timeout", vm_, __opts__, 900, ) if not salt.utils.cloud.wait_for_port(ip_address, timeout=ssh_connect_timeout): raise SaltCloudSystemExit("Failed to authenticate against remote ssh") pass_conn = get_conn(service="SoftLayer_Account") mask = { "virtualGuests": { "powerState": "", "operatingSystem": { "passwords": "" } }, } def get_passwd(): """ Wait for the password to become available """ node_info = pass_conn.getVirtualGuests(id=response["id"], mask=mask) for node in node_info: if (node["id"] == response["id"] and "passwords" in node["operatingSystem"] and node["operatingSystem"]["passwords"]): return node["operatingSystem"]["passwords"][0]["password"] time.sleep(5) return False passwd = salt.utils.cloud.wait_for_fun( get_passwd, timeout=config.get_cloud_config_value("wait_for_fun_timeout", vm_, __opts__, default=15 * 60), ) response["password"] = passwd response["public_ip"] = ip_address ssh_username = config.get_cloud_config_value("ssh_username", vm_, __opts__, default="root") vm_["ssh_host"] = ip_address vm_["password"] = passwd ret = __utils__["cloud.bootstrap"](vm_, __opts__) ret.update(response) __utils__["cloud.fire_event"]( "event", "created instance", "salt/cloud/{0}/created".format(name), args=__utils__["cloud.filter_event"]( "created", vm_, ["name", "profile", "provider", "driver"]), sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) return ret
def _salt(fun, *args, **kw): '''Execute a salt function on a specific minion Special kwargs: salt_target target to exec things on salt_timeout timeout for jobs salt_job_poll poll interval to wait for job finish result ''' try: poll = kw.pop('salt_job_poll') except KeyError: poll = 0.1 try: target = kw.pop('salt_target') except KeyError: target = None try: timeout = int(kw.pop('salt_timeout')) except (KeyError, ValueError): # try to has some low timeouts for very basic commands timeout = __FUN_TIMEOUT.get( fun, 900 # wait up to 15 minutes for the default timeout ) try: kwargs = kw.pop('kwargs') except KeyError: kwargs = {} if not target: infos = get_configured_provider() if not infos: return target = infos['target'] laps = time.time() cache = False if fun in __CACHED_FUNS: cache = True laps = laps // __CACHED_FUNS[fun] try: sargs = json.dumps(args) except TypeError: sargs = '' try: skw = json.dumps(kw) except TypeError: skw = '' try: skwargs = json.dumps(kwargs) except TypeError: skwargs = '' cache_key = (laps, target, fun, sargs, skw, skwargs) if not cache or (cache and (cache_key not in __CACHED_CALLS)): conn = _client() runner = _runner() rkwargs = kwargs.copy() rkwargs['timeout'] = timeout rkwargs.setdefault('expr_form', 'list') kwargs.setdefault('expr_form', 'list') ping_retries = 0 # the target(s) have environ one minute to respond # we call 60 ping request, this prevent us # from blindly send commands to unmatched minions ping_max_retries = 60 ping = True # do not check ping... if we are pinguing if fun == 'test.ping': ping_retries = ping_max_retries + 1 # be sure that the executors are alive while ping_retries <= ping_max_retries: try: if ping_retries > 0: time.sleep(1) pings = conn.cmd(tgt=target, timeout=10, fun='test.ping') values = list(pings.values()) if not values: ping = False for v in values: if v is not True: ping = False if not ping: raise ValueError('Unreachable') break except Exception: ping = False ping_retries += 1 log.error('{0} unreachable, retrying'.format(target)) if not ping: raise SaltCloudSystemExit('Target {0} unreachable'.format(target)) jid = conn.cmd_async(tgt=target, fun=fun, arg=args, kwarg=kw, **rkwargs) cret = conn.cmd(tgt=target, fun='saltutil.find_job', arg=[jid], timeout=10, **kwargs) running = bool(cret.get(target, False)) endto = time.time() + timeout while running: rkwargs = { 'tgt': target, 'fun': 'saltutil.find_job', 'arg': [jid], 'timeout': 10 } cret = conn.cmd(**rkwargs) running = bool(cret.get(target, False)) if not running: break if running and (time.time() > endto): raise Exception('Timeout {0}s for {1} is elapsed'.format( timeout, pformat(rkwargs))) time.sleep(poll) # timeout for the master to return data about a specific job wait_for_res = float({ 'test.ping': '5', }.get(fun, '120')) while wait_for_res: wait_for_res -= 0.5 cret = runner.cmd('jobs.lookup_jid', [jid, {'__kwarg__': True}]) if target in cret: ret = cret[target] break # recent changes elif 'data' in cret and 'outputter' in cret: ret = cret['data'] break # special case, some answers may be crafted # to handle the unresponsivness of a specific command # which is also meaningful, e.g. a minion not yet provisioned if fun in ['test.ping'] and not wait_for_res: ret = { 'test.ping': False, }.get(fun, False) time.sleep(0.5) try: if 'is not available.' in ret: raise SaltCloudSystemExit( 'module/function {0} is not available'.format(fun)) except SaltCloudSystemExit: raise except TypeError: pass if cache: __CACHED_CALLS[cache_key] = ret elif cache and cache_key in __CACHED_CALLS: ret = __CACHED_CALLS[cache_key] return ret
def create(server_): ''' Create a single BareMetal server from a data dict. ''' try: # Check for required profile parameters before sending any API calls. if server_['profile'] and config.is_profile_configured( __opts__, __active_provider_name__ or 'scaleway', server_['profile'], vm_=server_) is False: return False except AttributeError: pass __utils__['cloud.fire_event']( 'event', 'starting create', 'salt/cloud/{0}/creating'.format(server_['name']), args=__utils__['cloud.filter_event']( 'creating', server_, ['name', 'profile', 'provider', 'driver']), sock_dir=__opts__['sock_dir'], transport=__opts__['transport']) log.info('Creating a BareMetal server %s', server_['name']) access_key = config.get_cloud_config_value('access_key', get_configured_provider(), __opts__, search_global=False) commercial_type = config.get_cloud_config_value('commercial_type', server_, __opts__, default='C1') key_filename = config.get_cloud_config_value('ssh_key_file', server_, __opts__, search_global=False, default=None) if key_filename is not None and not os.path.isfile(key_filename): raise SaltCloudConfigError( 'The defined key_filename \'{0}\' does not exist'.format( key_filename)) ssh_password = config.get_cloud_config_value('ssh_password', server_, __opts__) kwargs = { 'name': server_['name'], 'organization': access_key, 'image': get_image(server_), 'commercial_type': commercial_type, } __utils__['cloud.fire_event']( 'event', 'requesting instance', 'salt/cloud/{0}/requesting'.format(server_['name']), args={ 'kwargs': __utils__['cloud.filter_event']('requesting', kwargs, list(kwargs)), }, sock_dir=__opts__['sock_dir'], transport=__opts__['transport']) try: ret = create_node(kwargs) except Exception as exc: log.error( 'Error creating %s on Scaleway\n\n' 'The following exception was thrown when trying to ' 'run the initial deployment: %s', server_['name'], exc, # Show the traceback if the debug logging level is enabled exc_info_on_loglevel=logging.DEBUG) return False def __query_node_data(server_name): ''' Called to check if the server has a public IP address. ''' data = show_instance(server_name, 'action') if data and data.get('public_ip'): return data return False try: data = salt.utils.cloud.wait_for_ip( __query_node_data, update_args=(server_['name'], ), timeout=config.get_cloud_config_value('wait_for_ip_timeout', server_, __opts__, default=10 * 60), interval=config.get_cloud_config_value('wait_for_ip_interval', server_, __opts__, default=10), ) except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc: try: # It might be already up, let's destroy it! destroy(server_['name']) except SaltCloudSystemExit: pass finally: raise SaltCloudSystemExit(six.text_type(exc)) server_['ssh_host'] = data['public_ip']['address'] server_['ssh_password'] = ssh_password server_['key_filename'] = key_filename ret = __utils__['cloud.bootstrap'](server_, __opts__) ret.update(data) log.info('Created BareMetal server \'%s\'', server_['name']) log.debug('\'%s\' BareMetal server creation details:\n%s', server_['name'], pprint.pformat(data)) __utils__['cloud.fire_event']( 'event', 'created instance', 'salt/cloud/{0}/created'.format(server_['name']), args=__utils__['cloud.filter_event']( 'created', server_, ['name', 'profile', 'provider', 'driver']), sock_dir=__opts__['sock_dir'], transport=__opts__['transport']) return ret
def create(server_): """ Create a single BareMetal server from a data dict. """ try: # Check for required profile parameters before sending any API calls. if (server_["profile"] and config.is_profile_configured( __opts__, __active_provider_name__ or "scaleway", server_["profile"], vm_=server_, ) is False): return False except AttributeError: pass __utils__["cloud.fire_event"]( "event", "starting create", "salt/cloud/{}/creating".format(server_["name"]), args=__utils__["cloud.filter_event"]( "creating", server_, ["name", "profile", "provider", "driver"]), sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) log.info("Creating a BareMetal server %s", server_["name"]) access_key = config.get_cloud_config_value("access_key", get_configured_provider(), __opts__, search_global=False) commercial_type = config.get_cloud_config_value("commercial_type", server_, __opts__, default="C1") key_filename = config.get_cloud_config_value("ssh_key_file", server_, __opts__, search_global=False, default=None) if key_filename is not None and not os.path.isfile(key_filename): raise SaltCloudConfigError( "The defined key_filename '{}' does not exist".format( key_filename)) ssh_password = config.get_cloud_config_value("ssh_password", server_, __opts__) kwargs = { "name": server_["name"], "organization": access_key, "image": get_image(server_), "commercial_type": commercial_type, } __utils__["cloud.fire_event"]( "event", "requesting instance", "salt/cloud/{}/requesting".format(server_["name"]), args={ "kwargs": __utils__["cloud.filter_event"]("requesting", kwargs, list(kwargs)), }, sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) try: ret = create_node(kwargs) except Exception as exc: # pylint: disable=broad-except log.error( "Error creating %s on Scaleway\n\n" "The following exception was thrown when trying to " "run the initial deployment: %s", server_["name"], exc, # Show the traceback if the debug logging level is enabled exc_info_on_loglevel=logging.DEBUG, ) return False def __query_node_data(server_name): """ Called to check if the server has a public IP address. """ data = show_instance(server_name, "action") if data and data.get("public_ip"): return data return False try: data = salt.utils.cloud.wait_for_ip( __query_node_data, update_args=(server_["name"], ), timeout=config.get_cloud_config_value("wait_for_ip_timeout", server_, __opts__, default=10 * 60), interval=config.get_cloud_config_value("wait_for_ip_interval", server_, __opts__, default=10), ) except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc: try: # It might be already up, let's destroy it! destroy(server_["name"]) except SaltCloudSystemExit: pass finally: raise SaltCloudSystemExit(str(exc)) server_["ssh_host"] = data["public_ip"]["address"] server_["ssh_password"] = ssh_password server_["key_filename"] = key_filename ret = __utils__["cloud.bootstrap"](server_, __opts__) ret.update(data) log.info("Created BareMetal server '%s'", server_["name"]) log.debug( "'%s' BareMetal server creation details:\n%s", server_["name"], pprint.pformat(data), ) __utils__["cloud.fire_event"]( "event", "created instance", "salt/cloud/{}/created".format(server_["name"]), args=__utils__["cloud.filter_event"]( "created", server_, ["name", "profile", "provider", "driver"]), sock_dir=__opts__["sock_dir"], transport=__opts__["transport"], ) return ret
def create_lb(kwargs=None, call=None): r''' Create a load-balancer configuration. CLI Example: .. code-block:: bash salt-cloud -f create_lb dimensiondata \ name=dev-lb port=80 protocol=http \ members=w1,w2,w3 algorithm=ROUND_ROBIN ''' conn = get_conn() if call != 'function': raise SaltCloudSystemExit( 'The create_lb function must be called with -f or --function.' ) if not kwargs or 'name' not in kwargs: log.error( 'A name must be specified when creating a health check.' ) return False if 'port' not in kwargs: log.error( 'A port or port-range must be specified for the load-balancer.' ) return False if 'networkdomain' not in kwargs: log.error( 'A network domain must be specified for the load-balancer.' ) return False if 'members' in kwargs: members = [] ip = "" membersList = kwargs.get('members').split(',') log.debug('MemberList: %s', membersList) for member in membersList: try: log.debug('Member: %s', member) node = get_node(conn, member) log.debug('Node: %s', node) ip = node.private_ips[0] except Exception as err: log.error( 'Failed to get node ip: %s', err, # Show the traceback if the debug logging level is enabled exc_info_on_loglevel=logging.DEBUG ) members.append(Member(ip, ip, kwargs['port'])) else: members = None log.debug('Members: %s', members) networkdomain = kwargs['networkdomain'] name = kwargs['name'] port = kwargs['port'] protocol = kwargs.get('protocol', None) algorithm = kwargs.get('algorithm', None) lb_conn = get_lb_conn(conn) network_domains = conn.ex_list_network_domains() network_domain = [y for y in network_domains if y.name == networkdomain][0] log.debug('Network Domain: %s', network_domain.id) lb_conn.ex_set_current_network_domain(network_domain.id) __utils__['cloud.fire_event']( 'event', 'create load_balancer', 'salt/cloud/loadbalancer/creating', args=kwargs, sock_dir=__opts__['sock_dir'], transport=__opts__['transport'] ) lb = lb_conn.create_balancer( name, port, protocol, algorithm, members ) __utils__['cloud.fire_event']( 'event', 'created load_balancer', 'salt/cloud/loadbalancer/created', args=kwargs, sock_dir=__opts__['sock_dir'], transport=__opts__['transport'] ) return _expand_balancer(lb)
def create(vm_): ''' Create a single VM from a data dict CLI Example: .. code-block:: bash salt-cloud -p profile_name vm_name ''' deploy = config.get_cloud_config_value('deploy', vm_, __opts__) key_filename = config.get_cloud_config_value('private_key', vm_, __opts__, search_global=False, default=None) if deploy is True and key_filename is None and \ salt.utils.which('sshpass') is None: raise SaltCloudSystemExit( 'Cannot deploy salt in a VM if the \'private_key\' setting ' 'is not set and \'sshpass\' binary is not present on the ' 'system for the password.') salt.utils.cloud.fire_event('event', 'starting create', 'salt/cloud/{0}/creating'.format(vm_['name']), { 'name': vm_['name'], 'profile': vm_['profile'], 'provider': vm_['provider'], }, transport=__opts__['transport']) log.info('Creating Cloud VM {0} in {1}'.format( vm_['name'], vm_.get('location', DEFAULT_LOCATION))) ## added . for fqdn hostnames salt.utils.cloud.check_name(vm_['name'], 'a-zA-Z0-9-.') kwargs = { 'name': vm_['name'], 'image': get_image(vm_), 'size': get_size(vm_), 'location': vm_.get('location', DEFAULT_LOCATION) } salt.utils.cloud.fire_event('event', 'requesting instance', 'salt/cloud/{0}/requesting'.format( vm_['name']), {'kwargs': kwargs}, transport=__opts__['transport']) try: data = create_node(**kwargs) except Exception as exc: log.error( 'Error creating {0} on JOYENT\n\n' 'The following exception was thrown when trying to ' 'run the initial deployment: \n{1}'.format(vm_['name'], str(exc)), # Show the traceback if the debug logging level is enabled exc_info_on_loglevel=logging.DEBUG) return False ret = {} def __query_node_data(vm_id, vm_location): rcode, data = query(command='my/machines/{0}'.format(vm_id), method='GET', location=vm_location) if rcode not in VALID_RESPONSE_CODES: # Trigger a wait for IP error return False if data['state'] != 'running': # Still not running, trigger another iteration return if isinstance(data['ips'], list) and len(data['ips']) > 0: return data if 'ips' in data: if isinstance(data['ips'], list) and len(data['ips']) <= 0: log.info('New joyent asynchronous machine creation api detected...' '\n\t\t-- please wait for IP addresses to be assigned...') try: data = salt.utils.cloud.wait_for_ip( __query_node_data, update_args=(data['id'], vm_.get('location', DEFAULT_LOCATION)), timeout=config.get_cloud_config_value('wait_for_ip_timeout', vm_, __opts__, default=5 * 60), interval=config.get_cloud_config_value('wait_for_ip_interval', vm_, __opts__, default=1), ) except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc: try: # It might be already up, let's destroy it! destroy(vm_['name']) except SaltCloudSystemExit: pass finally: raise SaltCloudSystemExit(str(exc)) data = reformat_node(data) ssh_username = config.get_cloud_config_value('ssh_username', vm_, __opts__, default='root') if config.get_cloud_config_value('deploy', vm_, __opts__) is True: host = data['public_ips'][0] salt_host = data['public_ips'][0] if ssh_interface(vm_) == 'private_ips': host = data['private_ips'][0] if get_salt_interface(vm_) == 'private_ips': salt_host = data['private_ips'][0] deploy_script = script(vm_) deploy_kwargs = { 'opts': __opts__, 'host': host, 'salt_host': salt_host, 'username': ssh_username, 'key_filename': key_filename, 'script': deploy_script.script, 'name': vm_['name'], 'tmp_dir': config.get_cloud_config_value('tmp_dir', vm_, __opts__, default='/tmp/.saltcloud'), 'deploy_command': config.get_cloud_config_value( 'deploy_command', vm_, __opts__, default='/tmp/.saltcloud/deploy.sh', ), 'start_action': __opts__['start_action'], 'parallel': __opts__['parallel'], 'sock_dir': __opts__['sock_dir'], 'conf_file': __opts__['conf_file'], 'minion_pem': vm_['priv_key'], 'minion_pub': vm_['pub_key'], 'keep_tmp': __opts__['keep_tmp'], 'preseed_minion_keys': vm_.get('preseed_minion_keys', None), 'sudo': config.get_cloud_config_value('sudo', vm_, __opts__, default=(ssh_username != 'root')), 'sudo_password': config.get_cloud_config_value('sudo_password', vm_, __opts__, default=None), 'tty': config.get_cloud_config_value('tty', vm_, __opts__, default=True), 'display_ssh_output': config.get_cloud_config_value('display_ssh_output', vm_, __opts__, default=True), 'script_args': config.get_cloud_config_value('script_args', vm_, __opts__), 'script_env': config.get_cloud_config_value('script_env', vm_, __opts__), 'minion_conf': salt.utils.cloud.minion_config(__opts__, vm_) } # Deploy salt-master files, if necessary if config.get_cloud_config_value('make_master', vm_, __opts__) is True: deploy_kwargs['make_master'] = True deploy_kwargs['master_pub'] = vm_['master_pub'] deploy_kwargs['master_pem'] = vm_['master_pem'] master_conf = salt.utils.cloud.master_config(__opts__, vm_) deploy_kwargs['master_conf'] = master_conf if master_conf.get('syndic_master', None): deploy_kwargs['make_syndic'] = True deploy_kwargs['make_minion'] = config.get_cloud_config_value( 'make_minion', vm_, __opts__, default=True) # Check for Windows install params win_installer = config.get_cloud_config_value('win_installer', vm_, __opts__) if win_installer: deploy_kwargs['win_installer'] = win_installer minion = salt.utils.cloud.minion_config(__opts__, vm_) deploy_kwargs['master'] = minion['master'] deploy_kwargs['username'] = config.get_cloud_config_value( 'win_username', vm_, __opts__, default='Administrator') deploy_kwargs['password'] = config.get_cloud_config_value( 'win_password', vm_, __opts__, default='') # Store what was used to the deploy the VM event_kwargs = copy.deepcopy(deploy_kwargs) del event_kwargs['minion_pem'] del event_kwargs['minion_pub'] del event_kwargs['sudo_password'] if 'password' in event_kwargs: del event_kwargs['password'] ret['deploy_kwargs'] = event_kwargs salt.utils.cloud.fire_event('event', 'executing deploy script', 'salt/cloud/{0}/deploying'.format( vm_['name']), {'kwargs': event_kwargs}, transport=__opts__['transport']) deployed = False if win_installer: deployed = salt.utils.cloud.deploy_windows(**deploy_kwargs) else: deployed = salt.utils.cloud.deploy_script(**deploy_kwargs) if deployed: log.info('Salt installed on {0}'.format(vm_['name'])) else: log.error('Failed to start Salt on Cloud VM {0}'.format( vm_['name'])) ret.update(data) log.info('Created Cloud VM {0[name]!r}'.format(vm_)) log.debug('{0[name]!r} VM creation details:\n{1}'.format( vm_, pprint.pformat(data))) salt.utils.cloud.fire_event('event', 'created instance', 'salt/cloud/{0}/created'.format(vm_['name']), { 'name': vm_['name'], 'profile': vm_['profile'], 'provider': vm_['provider'], }, transport=__opts__['transport']) return ret
def destroy(name, call=None): ''' Destroy a node. .. versionadded:: 2018.3.0 Disconnect a minion from the master, and remove its keys. Optionally, (if ``remove_config_on_destroy`` is ``True``), disables salt-minion from running on the minion, and erases the Salt configuration files from it. Optionally, (if ``shutdown_on_destroy`` is ``True``), orders the minion to halt. CLI Example: .. code-block:: bash salt-cloud --destroy mymachine ''' if call == 'function': raise SaltCloudSystemExit( 'The destroy action must be called with -d, --destroy, ' '-a, or --action.') opts = __opts__ __utils__['cloud.fire_event']('event', 'destroying instance', 'salt/cloud/{0}/destroying'.format(name), args={ 'name': name }, sock_dir=opts['sock_dir'], transport=opts['transport']) vm_ = get_configured_provider() local = salt.client.LocalClient() my_info = local.cmd(name, 'grains.get', ['salt-cloud']) try: vm_.update(my_info[name]) # get profile name to get config value except (IndexError, TypeError): pass if config.get_cloud_config_value('remove_config_on_destroy', vm_, opts, default=True): ret = local.cmd( name, # prevent generating new keys on restart 'service.disable', ['salt-minion']) if ret and ret[name]: log.info('disabled salt-minion service on %s', name) ret = local.cmd(name, 'config.get', ['conf_file']) if ret and ret[name]: confile = ret[name] ret = local.cmd(name, 'file.remove', [confile]) if ret and ret[name]: log.info('removed minion %s configuration file %s', name, confile) ret = local.cmd(name, 'config.get', ['pki_dir']) if ret and ret[name]: pki_dir = ret[name] ret = local.cmd(name, 'file.remove', [pki_dir]) if ret and ret[name]: log.info('removed minion %s key files in %s', name, pki_dir) if config.get_cloud_config_value('shutdown_on_destroy', vm_, opts, default=False): ret = local.cmd(name, 'system.shutdown') if ret and ret[name]: log.info('system.shutdown for minion %s successful', name) __utils__['cloud.fire_event']('event', 'destroyed instance', 'salt/cloud/{0}/destroyed'.format(name), args={ 'name': name }, sock_dir=opts['sock_dir'], transport=opts['transport']) return {'Destroyed': '{0} was destroyed.'.format(name)}
def destroy(name, conn=None, call=None): ''' Delete a single VM ''' if call == 'function': raise SaltCloudSystemExit( 'The destroy action must be called with -d, --destroy, ' '-a or --action.') __utils__['cloud.fire_event']('event', 'destroying instance', 'salt/cloud/{0}/destroying'.format(name), args={ 'name': name }, sock_dir=__opts__['sock_dir'], transport=__opts__['transport']) if not conn: conn = get_conn() # pylint: disable=E0602 node = conn.server_by_name(name) profiles = get_configured_provider()['profiles'] # pylint: disable=E0602 if node is None: log.error('Unable to find the VM %s', name) profile = None if 'metadata' in node.extra and 'profile' in node.extra['metadata']: profile = node.extra['metadata']['profile'] flush_mine_on_destroy = False if profile and profile in profiles and 'flush_mine_on_destroy' in profiles[ profile]: flush_mine_on_destroy = profiles[profile]['flush_mine_on_destroy'] if flush_mine_on_destroy: log.info('Clearing Salt Mine: %s', name) salt_client = salt.client.get_local_client(__opts__['conf_file']) minions = salt_client.cmd(name, 'mine.flush') log.info('Clearing Salt Mine: %s, %s', name, flush_mine_on_destroy) log.info('Destroying VM: %s', name) ret = conn.delete(node.id) if ret: log.info('Destroyed VM: %s', name) # Fire destroy action __utils__['cloud.fire_event']('event', 'destroyed instance', 'salt/cloud/{0}/destroyed'.format(name), args={ 'name': name }, sock_dir=__opts__['sock_dir'], transport=__opts__['transport']) if __opts__.get('delete_sshkeys', False) is True: salt.utils.cloud.remove_sshkey( getattr(node, __opts__.get('ssh_interface', 'public_ips'))[0]) if __opts__.get('update_cachedir', False) is True: __utils__['cloud.delete_minion_cachedir']( name, __active_provider_name__.split(':')[0], __opts__) __utils__['cloud.cachedir_index_del'](name) return True log.error('Failed to Destroy VM: %s', name) return False
def create(vm_): ''' Create a single VM from a data dict ''' try: # Check for required profile parameters before sending any API calls. if vm_['profile'] and config.is_profile_configured( __opts__, __active_provider_name__ or 'nova', vm_['profile'], vm_=vm_) is False: return False except AttributeError: pass deploy = config.get_cloud_config_value('deploy', vm_, __opts__) key_filename = config.get_cloud_config_value('ssh_key_file', vm_, __opts__, search_global=False, default=None) if key_filename is not None and not os.path.isfile(key_filename): raise SaltCloudConfigError( 'The defined ssh_key_file \'{0}\' does not exist'.format( key_filename)) vm_['key_filename'] = key_filename # Since using "provider: <provider-engine>" is deprecated, alias provider # to use driver: "driver: <provider-engine>" if 'provider' in vm_: vm_['driver'] = vm_.pop('provider') salt.utils.cloud.fire_event('event', 'starting create', 'salt/cloud/{0}/creating'.format(vm_['name']), { 'name': vm_['name'], 'profile': vm_['profile'], 'provider': vm_['driver'], }, transport=__opts__['transport']) conn = get_conn() if 'instance_id' in vm_: # This was probably created via another process, and doesn't have # things like salt keys created yet, so let's create them now. if 'pub_key' not in vm_ and 'priv_key' not in vm_: log.debug('Generating minion keys for \'{0[name]}\''.format(vm_)) vm_['priv_key'], vm_['pub_key'] = salt.utils.cloud.gen_keys( salt.config.get_cloud_config_value('keysize', vm_, __opts__)) data = conn.server_show_libcloud(vm_['instance_id']) if vm_['key_filename'] is None and 'change_password' in __opts__ and __opts__[ 'change_password'] is True: vm_['password'] = sup.secure_password() conn.root_password(vm_['instance_id'], vm_['password']) else: # Put together all of the information required to request the instance, # and then fire off the request for it data, vm_ = request_instance(vm_) # Pull the instance ID, valid for both spot and normal instances vm_['instance_id'] = data.id def __query_node_data(vm_, data): try: node = show_instance(vm_['name'], 'action') log.debug('Loaded node data for {0}:\n{1}'.format( vm_['name'], pprint.pformat(node))) except Exception as err: log.error( 'Failed to get nodes list: {0}'.format(err), # Show the traceback if the debug logging level is enabled exc_info_on_loglevel=logging.DEBUG) # Trigger a failure in the wait for IP function return False running = node['state'] == 'ACTIVE' if not running: # Still not running, trigger another iteration return if rackconnect(vm_) is True: extra = node.get('extra', {}) rc_status = extra.get('metadata', {}).get('rackconnect_automation_status', '') if rc_status != 'DEPLOYED': log.debug('Waiting for Rackconnect automation to complete') return if managedcloud(vm_) is True: extra = conn.server_show_libcloud(node['id']).extra mc_status = extra.get('metadata', {}).get('rax_service_level_automation', '') if mc_status != 'Complete': log.debug('Waiting for managed cloud automation to complete') return access_ip = node.get('extra', {}).get('access_ip', '') rcv3 = rackconnectv3(vm_) in node['addresses'] sshif = ssh_interface(vm_) in node['addresses'] if any((rcv3, sshif)): networkname = rackconnectv3(vm_) if rcv3 else ssh_interface(vm_) for network in node['addresses'].get(networkname, []): if network['version'] is 4: access_ip = network['addr'] break vm_['cloudnetwork'] = True # Conditions to pass this # # Rackconnect v2: vm_['rackconnect'] = True # If this is True, then the server will not be accessible from the ipv4 addres in public_ips. # That interface gets turned off, and an ipv4 from the dedicated firewall is routed to the # server. In this case we can use the private_ips for ssh_interface, or the access_ip. # # Rackconnect v3: vm['rackconnectv3'] = <cloudnetwork> # If this is the case, salt will need to use the cloud network to login to the server. There # is no ipv4 address automatically provisioned for these servers when they are booted. SaltCloud # also cannot use the private_ips, because that traffic is dropped at the hypervisor. # # CloudNetwork: vm['cloudnetwork'] = True # If this is True, then we should have an access_ip at this point set to the ip on the cloud # network. If that network does not exist in the 'addresses' dictionary, then SaltCloud will # use the initial access_ip, and not overwrite anything. if any((cloudnetwork(vm_), rackconnect(vm_))) and (ssh_interface(vm_) != 'private_ips' or rcv3): data.public_ips = [ access_ip, ] return data result = [] if 'private_ips' not in node and 'public_ips' not in node and \ 'floating_ips' not in node and 'fixed_ips' not in node and \ 'access_ip' in node.get('extra', {}): result = [node['extra']['access_ip']] private = node.get('private_ips', []) public = node.get('public_ips', []) fixed = node.get('fixed_ips', []) floating = node.get('floating_ips', []) if private and not public: log.warning('Private IPs returned, but not public... Checking for ' 'misidentified IPs') for private_ip in private: private_ip = preferred_ip(vm_, [private_ip]) if salt.utils.cloud.is_public_ip(private_ip): log.warning('{0} is a public IP'.format(private_ip)) data.public_ips.append(private_ip) log.warning( ('Public IP address was not ready when we last' ' checked. Appending public IP address now.')) public = data.public_ips else: log.warning('{0} is a private IP'.format(private_ip)) ignore_ip = ignore_cidr(vm_, private_ip) if private_ip not in data.private_ips and not ignore_ip: result.append(private_ip) # populate return data with private_ips # when ssh_interface is set to private_ips and public_ips exist if not result and ssh_interface(vm_) == 'private_ips': for private_ip in private: ignore_ip = ignore_cidr(vm_, private_ip) if private_ip not in data.private_ips and not ignore_ip: result.append(private_ip) non_private_ips = [] if public: data.public_ips = public if ssh_interface(vm_) == 'public_ips': non_private_ips.append(public) if floating: data.floating_ips = floating if ssh_interface(vm_) == 'floating_ips': non_private_ips.append(floating) if fixed: data.fixed_ips = fixed if ssh_interface(vm_) == 'fixed_ips': non_private_ips.append(fixed) if non_private_ips: log.debug('result = {0}'.format(non_private_ips)) data.private_ips = result if ssh_interface(vm_) != 'private_ips': return data if result: log.debug('result = {0}'.format(result)) data.private_ips = result if ssh_interface(vm_) == 'private_ips': return data try: data = salt.utils.cloud.wait_for_ip( __query_node_data, update_args=(vm_, data), timeout=config.get_cloud_config_value('wait_for_ip_timeout', vm_, __opts__, default=10 * 60), interval=config.get_cloud_config_value('wait_for_ip_interval', vm_, __opts__, default=10), ) except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc: try: # It might be already up, let's destroy it! destroy(vm_['name']) except SaltCloudSystemExit: pass finally: raise SaltCloudSystemExit(str(exc)) log.debug('VM is now running') if ssh_interface(vm_) == 'private_ips': ip_address = preferred_ip(vm_, data.private_ips) elif ssh_interface(vm_) == 'fixed_ips': ip_address = preferred_ip(vm_, data.fixed_ips) elif ssh_interface(vm_) == 'floating_ips': ip_address = preferred_ip(vm_, data.floating_ips) else: ip_address = preferred_ip(vm_, data.public_ips) log.debug('Using IP address {0}'.format(ip_address)) if salt.utils.cloud.get_salt_interface(vm_, __opts__) == 'private_ips': salt_ip_address = preferred_ip(vm_, data.private_ips) log.info('Salt interface set to: {0}'.format(salt_ip_address)) elif salt.utils.cloud.get_salt_interface(vm_, __opts__) == 'fixed_ips': salt_ip_address = preferred_ip(vm_, data.fixed_ips) log.info('Salt interface set to: {0}'.format(salt_ip_address)) elif salt.utils.cloud.get_salt_interface(vm_, __opts__) == 'floating_ips': salt_ip_address = preferred_ip(vm_, data.floating_ips) log.info('Salt interface set to: {0}'.format(salt_ip_address)) else: salt_ip_address = preferred_ip(vm_, data.public_ips) log.debug('Salt interface set to: {0}'.format(salt_ip_address)) if not ip_address: raise SaltCloudSystemExit('A valid IP address was not found') vm_['ssh_host'] = ip_address vm_['salt_host'] = salt_ip_address ret = salt.utils.cloud.bootstrap(vm_, __opts__) ret.update(data.__dict__) if 'password' in ret['extra']: del ret['extra']['password'] log.info('Created Cloud VM \'{0[name]}\''.format(vm_)) log.debug('\'{0[name]}\' VM creation details:\n{1}'.format( vm_, pprint.pformat(data.__dict__))) event_data = { 'name': vm_['name'], 'profile': vm_['profile'], 'provider': vm_['driver'], 'instance_id': vm_['instance_id'], 'floating_ips': data.floating_ips, 'fixed_ips': data.fixed_ips, 'private_ips': data.private_ips, 'public_ips': data.public_ips } salt.utils.cloud.fire_event('event', 'created instance', 'salt/cloud/{0}/created'.format(vm_['name']), event_data, transport=__opts__['transport']) salt.utils.cloud.cachedir_index_add(vm_['name'], vm_['profile'], 'nova', vm_['driver']) return ret
def boot(name=None, kwargs=None, call=None): ''' Boot a Linode. name The name of the Linode to boot. Can be used instead of ``linode_id``. linode_id The ID of the Linode to boot. If provided, will be used as an alternative to ``name`` and reduces the number of API calls to Linode by one. Will be preferred over ``name``. config_id The ID of the Config to boot. Required. check_running Defaults to True. If set to False, overrides the call to check if the VM is running before calling the linode.boot API call. Change ``check_running`` to True is useful during the boot call in the create function, since the new VM will not be running yet. Can be called as an action (which requires a name): .. code-block:: bash salt-cloud -a boot my-instance config_id=10 ...or as a function (which requires either a name or linode_id): .. code-block:: bash salt-cloud -f boot my-linode-config name=my-instance config_id=10 salt-cloud -f boot my-linode-config linode_id=1225876 config_id=10 ''' if name is None and call == 'action': raise SaltCloudSystemExit( 'The boot action requires a \'name\'.' ) if kwargs is None: kwargs = {} linode_id = kwargs.get('linode_id', None) config_id = kwargs.get('config_id', None) check_running = kwargs.get('check_running', True) if call == 'function': name = kwargs.get('name', None) if name is None and linode_id is None: raise SaltCloudSystemExit( 'The boot function requires either a \'name\' or a \'linode_id\'.' ) if config_id is None: raise SaltCloudSystemExit( 'The boot function requires a \'config_id\'.' ) if linode_id is None: linode_id = get_linode_id_from_name(name) linode_item = name else: linode_item = linode_id # Check if Linode is running first if check_running is True: status = get_linode(kwargs={'linode_id': linode_id})['STATUS'] if status == '1': raise SaltCloudSystemExit( 'Cannot boot Linode {0}. ' 'Linode {0} is already running.'.format(linode_item) ) # Boot the VM and get the JobID from Linode response = _query('linode', 'boot', args={'LinodeID': linode_id, 'ConfigID': config_id})['DATA'] boot_job_id = response['JobID'] if not _wait_for_job(linode_id, boot_job_id): log.error('Boot failed for Linode {0}.'.format(linode_item)) return False return True
def request_instance(vm_=None, call=None): ''' Put together all of the information necessary to request an instance through Novaclient and then fire off the request the instance. Returns data about the instance ''' if call == 'function': # Technically this function may be called other ways too, but it # definitely cannot be called with --function. raise SaltCloudSystemExit( 'The request_instance action must be called with -a or --action.') log.info('Creating Cloud VM {0}'.format(vm_['name'])) salt.utils.cloud.check_name(vm_['name'], 'a-zA-Z0-9._-') conn = get_conn() kwargs = vm_.copy() try: kwargs['image_id'] = get_image(conn, vm_) except Exception as exc: raise SaltCloudSystemExit('Error creating {0} on OPENSTACK\n\n' 'Could not find image {1}: {2}\n'.format( vm_['name'], vm_['image'], exc)) try: kwargs['flavor_id'] = get_size(conn, vm_) except Exception as exc: raise SaltCloudSystemExit('Error creating {0} on OPENSTACK\n\n' 'Could not find size {1}: {2}\n'.format( vm_['name'], vm_['size'], exc)) kwargs['key_name'] = config.get_cloud_config_value('ssh_key_name', vm_, __opts__, search_global=False) security_groups = config.get_cloud_config_value('security_groups', vm_, __opts__, search_global=False) if security_groups is not None: vm_groups = security_groups.split(',') avail_groups = conn.secgroup_list() group_list = [] for vmg in vm_groups: if vmg in [name for name, details in six.iteritems(avail_groups)]: group_list.append(vmg) else: raise SaltCloudNotFound( 'No such security group: \'{0}\''.format(vmg)) kwargs['security_groups'] = group_list avz = config.get_cloud_config_value('availability_zone', vm_, __opts__, default=None, search_global=False) if avz is not None: kwargs['availability_zone'] = avz kwargs['nics'] = config.get_cloud_config_value('networks', vm_, __opts__, search_global=False, default=None) files = config.get_cloud_config_value('files', vm_, __opts__, search_global=False) if files: kwargs['files'] = {} for src_path in files: if os.path.exists(files[src_path]): with salt.utils.fopen(files[src_path], 'r') as fp_: kwargs['files'][src_path] = fp_.read() else: kwargs['files'][src_path] = files[src_path] userdata_file = config.get_cloud_config_value('userdata_file', vm_, __opts__, search_global=False) if userdata_file is not None: with salt.utils.fopen(userdata_file, 'r') as fp: kwargs['userdata'] = fp.read() kwargs['config_drive'] = config.get_cloud_config_value('config_drive', vm_, __opts__, search_global=False) kwargs.update(get_block_mapping_opts(vm_)) salt.utils.cloud.fire_event( 'event', 'requesting instance', 'salt/cloud/{0}/requesting'.format(vm_['name']), { 'kwargs': { 'name': kwargs['name'], 'image': kwargs.get('image_id', 'Boot From Volume'), 'size': kwargs['flavor_id'] } }, transport=__opts__['transport']) try: data = conn.boot(**kwargs) except Exception as exc: raise SaltCloudSystemExit( 'Error creating {0} on Nova\n\n' 'The following exception was thrown by libcloud when trying to ' 'run the initial deployment: {1}\n'.format(vm_['name'], exc)) if data.extra.get('password', None) is None and vm_.get( 'key_filename', None) is None: raise SaltCloudSystemExit('No password returned. Set ssh_key_file.') floating_ip_conf = config.get_cloud_config_value('floating_ip', vm_, __opts__, search_global=False) if floating_ip_conf.get('auto_assign', False): pool = floating_ip_conf.get('pool', 'public') for fl_ip, opts in conn.floating_ip_list().iteritems(): if opts['instance_id'] is None and opts['pool'] == pool: floating_ip = fl_ip break else: floating_ip = conn.floating_ip_create(pool) try: conn.floating_ip_associate(kwargs['name'], floating_ip) vm_['floating_ip'] = floating_ip except Exception as exc: raise SaltCloudSystemExit( 'Error assigning floating_ip for {0} on Nova\n\n' 'The following exception was thrown by libcloud when trying to ' 'assing a floating ip: {1}\n'.format(vm_['name'], exc)) vm_['password'] = data.extra.get('password', '') return data, vm_
def destroy(name, call=None): """ This function irreversibly destroys a virtual machine on the cloud provider. Before doing so, it should fire an event on the Salt event bus. The tag for this event is `salt/cloud/<vm name>/destroying`. Once the virtual machine has been destroyed, another event is fired. The tag for that event is `salt/cloud/<vm name>/destroyed`. Dependencies: list_nodes @param name: @type name: str @param call: @type call: @return: True if all went well, otherwise an error message @rtype: bool|str """ log.info("Attempting to delete instance {0}".format(name)) if call == 'function': raise SaltCloudSystemExit( 'The destroy action must be called with -d, --destroy, ' '-a or --action.' ) found = [] providers = __opts__.get('providers', {}) providers_to_check = [_f for _f in [cfg.get('libvirt') for cfg in six.itervalues(providers)] if _f] for provider in providers_to_check: conn = __get_conn(provider['url']) log.info("looking at {0}".format(provider['url'])) try: domain = conn.lookupByName(name) found.append({'domain': domain, 'conn': conn}) except libvirtError: pass if not found: return "{0} doesn't exist and can't be deleted".format(name) if len(found) > 1: return "{0} doesn't identify a unique machine leaving things".format(name) __utils__['cloud.fire_event']( 'event', 'destroying instance', 'salt/cloud/{0}/destroying'.format(name), {'name': name}, sock_dir=__opts__['sock_dir'], transport=__opts__['transport'] ) destroy_domain(found[0]['conn'], found[0]['domain']) __utils__['cloud.fire_event']( 'event', 'destroyed instance', 'salt/cloud/{0}/destroyed'.format(name), {'name': name}, sock_dir=__opts__['sock_dir'], transport=__opts__['transport'] )
def create(vm_): ''' Provision a single machine ''' clone_strategy = vm_.get('clone_strategy') or 'full' if clone_strategy not in set(['quick', 'full']): raise SaltCloudSystemExit("'clone_strategy' must be one of quick or full. Got '{0}'".format(clone_strategy)) ip_source = vm_.get('ip_source') or 'ip-learning' if ip_source not in set(['ip-learning', 'qemu-agent']): raise SaltCloudSystemExit("'ip_source' must be one of qemu-agent or ip-learning. Got '{0}'".format(ip_source)) log.info("Cloning machine '{0}' with strategy '{1}'".format(vm_['name'], clone_strategy)) try: # Check for required profile parameters before sending any API calls. if vm_['profile'] and config.is_profile_configured(__opts__, __active_provider_name__ or 'libvirt', vm_['profile']) is False: return False except AttributeError: pass # TODO: check name qemu/libvirt will choke on some characters (like '/')? name = vm_['name'] __utils__['cloud.fire_event']( 'event', 'starting create', 'salt/cloud/{0}/creating'.format(name), args=__utils__['cloud.filter_event']('creating', vm_, ['name', 'profile', 'provider', 'driver']), sock_dir=__opts__['sock_dir'], transport=__opts__['transport'] ) key_filename = config.get_cloud_config_value( 'private_key', vm_, __opts__, search_global=False, default=None ) if key_filename is not None and not os.path.isfile(key_filename): raise SaltCloudConfigError( 'The defined key_filename \'{0}\' does not exist'.format( key_filename ) ) vm_['key_filename'] = key_filename # wait_for_instance requires private_key vm_['private_key'] = key_filename cleanup = [] try: # clone the vm base = vm_['base_domain'] conn = __get_conn(vm_['url']) try: # for idempotency the salt-bootstrap needs -F argument # script_args: -F clone_domain = conn.lookupByName(name) except libvirtError as e: domain = conn.lookupByName(base) # TODO: ensure base is shut down before cloning xml = domain.XMLDesc(0) kwargs = { 'name': name, 'base_domain': base, } __utils__['cloud.fire_event']( 'event', 'requesting instance', 'salt/cloud/{0}/requesting'.format(name), args={ 'kwargs': __utils__['cloud.filter_event']('requesting', kwargs, kwargs.keys()), }, sock_dir=__opts__['sock_dir'], transport=__opts__['transport'] ) log.debug("Source machine XML '{0}'".format(xml)) domain_xml = ElementTree.fromstring(xml) domain_xml.find('./name').text = name if domain_xml.find('./description') is None: description_elem = ElementTree.Element('description') domain_xml.insert(0, description_elem) description = domain_xml.find('./description') description.text = "Cloned from {0}".format(base) domain_xml.remove(domain_xml.find('./uuid')) for iface_xml in domain_xml.findall('./devices/interface'): iface_xml.remove(iface_xml.find('./mac')) # enable IP learning, this might be a default behaviour... if iface_xml.find("./filterref/parameter[@name='CTRL_IP_LEARNING']") is None: iface_xml.append(ElementTree.fromstring(IP_LEARNING_XML)) # If a qemu agent is defined we need to fix the path to its socket # <channel type='unix'> # <source mode='bind' path='/var/lib/libvirt/qemu/channel/target/domain-<dom-name>/org.qemu.guest_agent.0'/> # <target type='virtio' name='org.qemu.guest_agent.0'/> # <address type='virtio-serial' controller='0' bus='0' port='2'/> # </channel> for agent_xml in domain_xml.findall("""./devices/channel[@type='unix']"""): # is org.qemu.guest_agent.0 an option? if agent_xml.find("""./target[@type='virtio'][@name='org.qemu.guest_agent.0']""") is not None: source_element = agent_xml.find("""./source[@mode='bind']""") # see if there is a path element that needs rewriting if source_element and 'path' in source_element.attrib: path = source_element.attrib['path'] new_path = path.replace('/domain-{0}/'.format(base), '/domain-{0}/'.format(name)) log.debug("Rewriting agent socket path to {0}".format(new_path)) source_element.attrib['path'] = new_path for disk in domain_xml.findall("""./devices/disk[@device='disk'][@type='file']"""): # print "Disk: ", ElementTree.tostring(disk) # check if we can clone driver = disk.find("./driver[@name='qemu']") if driver is None: # Err on the safe side raise SaltCloudExecutionFailure("Non qemu driver disk encountered bailing out.") disk_type = driver.attrib.get('type') log.info("disk attributes {0}".format(disk.attrib)) if disk_type == 'qcow2': source = disk.find("./source").attrib['file'] pool, volume = find_pool_and_volume(conn, source) if clone_strategy == 'quick': new_volume = pool.createXML(create_volume_with_backing_store_xml(volume), 0) else: new_volume = pool.createXMLFrom(create_volume_xml(volume), volume, 0) cleanup.append({'what': 'volume', 'item': new_volume}) disk.find("./source").attrib['file'] = new_volume.path() elif disk_type == 'raw': source = disk.find("./source").attrib['file'] pool, volume = find_pool_and_volume(conn, source) # TODO: more control on the cloned disk type new_volume = pool.createXMLFrom(create_volume_xml(volume), volume, 0) cleanup.append({'what': 'volume', 'item': new_volume}) disk.find("./source").attrib['file'] = new_volume.path() else: raise SaltCloudExecutionFailure("Disk type '{0}' not supported".format(disk_type)) log.debug("Clone XML '{0}'".format(domain_xml)) clone_xml = ElementTree.tostring(domain_xml) clone_domain = conn.defineXMLFlags(clone_xml, libvirt.VIR_DOMAIN_DEFINE_VALIDATE) cleanup.append({'what': 'domain', 'item': clone_domain}) clone_domain.createWithFlags(libvirt.VIR_DOMAIN_START_FORCE_BOOT) log.debug("VM '{0}'".format(vm_)) if ip_source == 'qemu-agent': ip_source = libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT elif ip_source == 'ip-learning': ip_source = libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE address = salt.utils.cloud.wait_for_ip( get_domain_ip, update_args=(clone_domain, 0, ip_source), timeout=config.get_cloud_config_value('wait_for_ip_timeout', vm_, __opts__, default=10 * 60), interval=config.get_cloud_config_value('wait_for_ip_interval', vm_, __opts__, default=10), interval_multiplier=config.get_cloud_config_value('wait_for_ip_interval_multiplier', vm_, __opts__, default=1), ) log.info('Address = {0}'.format(address)) vm_['ssh_host'] = address # the bootstrap script needs to be installed first in /etc/salt/cloud.deploy.d/ # salt-cloud -u is your friend ret = __utils__['cloud.bootstrap'](vm_, __opts__) __utils__['cloud.fire_event']( 'event', 'created instance', 'salt/cloud/{0}/created'.format(name), args=__utils__['cloud.filter_event']('created', vm_, ['name', 'profile', 'provider', 'driver']), sock_dir=__opts__['sock_dir'], transport=__opts__['transport'] ) return ret except Exception as e: # pylint: disable=broad-except # Try to clean up in as much cases as possible log.info('Cleaning up after exception clean up items: {0}'.format(cleanup)) for leftover in cleanup: what = leftover['what'] item = leftover['item'] if what == 'domain': destroy_domain(conn, item) if what == 'volume': item.delete() raise e
def create(vm_): ''' Create a single VM from a data dict ''' try: # Check for required profile parameters before sending any API calls. if vm_['profile'] and config.is_profile_configured( __opts__, __active_provider_name__ or 'opennebula', vm_['profile'], vm_=vm_) is False: return False except AttributeError: pass # Since using "provider: <provider-engine>" is deprecated, alias provider # to use driver: "driver: <provider-engine>" if 'provider' in vm_: vm_['driver'] = vm_.pop('provider') __utils__['cloud.fire_event']( 'event', 'starting create', 'salt/cloud/{0}/creating'.format(vm_['name']), { 'name': vm_['name'], 'profile': vm_['profile'], 'provider': vm_['driver'], }, ) log.info('Creating Cloud VM {0}'.format(vm_['name'])) kwargs = { 'name': vm_['name'], 'image_id': get_image(vm_), 'region_id': get_location(vm_), } private_networking = config.get_cloud_config_value('private_networking', vm_, __opts__, search_global=False, default=None) kwargs['private_networking'] = 'true' if private_networking else 'false' __utils__['cloud.fire_event']( 'event', 'requesting instance', 'salt/cloud/{0}/requesting'.format(vm_['name']), { 'kwargs': kwargs }, ) region = '' if kwargs['region_id'] is not None: region = 'SCHED_REQUIREMENTS="ID={0}"'.format(kwargs['region_id']) try: server, user, password = _get_xml_rpc() auth = ':'.join([user, password]) server.one.template.instantiate(auth, int(kwargs['image_id']), kwargs['name'], False, region) except Exception as exc: log.error( 'Error creating {0} on OpenNebula\n\n' 'The following exception was thrown when trying to ' 'run the initial deployment: {1}'.format(vm_['name'], str(exc)), # Show the traceback if the debug logging level is enabled exc_info_on_loglevel=logging.DEBUG) return False def __query_node_data(vm_name): node_data = show_instance(vm_name, call='action') if not node_data: # Trigger an error in the wait_for_ip function return False if node_data['state'] == '7': return False if node_data['lcm_state'] == '3': return node_data try: data = __utils__['cloud.wait_for_ip']( __query_node_data, update_args=(vm_['name'], ), timeout=config.get_cloud_config_value('wait_for_ip_timeout', vm_, __opts__, default=10 * 60), interval=config.get_cloud_config_value('wait_for_ip_interval', vm_, __opts__, default=2), ) except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc: try: # It might be already up, let's destroy it! destroy(vm_['name']) except SaltCloudSystemExit: pass finally: raise SaltCloudSystemExit(str(exc)) key_filename = config.get_cloud_config_value('private_key', vm_, __opts__, search_global=False, default=None) if key_filename is not None and not os.path.isfile(key_filename): raise SaltCloudConfigError( 'The defined key_filename {0!r} does not exist'.format( key_filename)) try: private_ip = data['private_ips'][0] except KeyError: private_ip = data['template']['nic']['ip'] ssh_username = config.get_cloud_config_value('ssh_username', vm_, __opts__, default='root') vm_['username'] = ssh_username vm_['key_filename'] = key_filename vm_['ssh_host'] = private_ip ret = __utils__['cloud.bootstrap'](vm_, __opts__) ret['id'] = data['id'] ret['image'] = vm_['image'] ret['name'] = vm_['name'] ret['size'] = data['template']['memory'] ret['state'] = data['state'] ret['private_ips'] = private_ip ret['public_ips'] = [] log.info('Created Cloud VM {0[name]!r}'.format(vm_)) log.debug('{0[name]!r} VM creation details:\n{1}'.format( vm_, pprint.pformat(data))) __utils__['cloud.fire_event']( 'event', 'created instance', 'salt/cloud/{0}/created'.format(vm_['name']), { 'name': vm_['name'], 'profile': vm_['profile'], 'provider': vm_['driver'], }, ) return ret
def request_instance(vm_=None, call=None): ''' Put together all of the information necessary to request an instance through Novaclient and then fire off the request the instance. Returns data about the instance ''' if call == 'function': # Technically this function may be called other ways too, but it # definitely cannot be called with --function. raise SaltCloudSystemExit( 'The request_instance action must be called with -a or --action.') log.info('Creating Cloud VM %s', vm_['name']) salt.utils.cloud.check_name(vm_['name'], 'a-zA-Z0-9._-') conn = get_conn() kwargs = vm_.copy() try: kwargs['image_id'] = get_image(conn, vm_) except Exception as exc: raise SaltCloudSystemExit('Error creating {0} on OPENSTACK\n\n' 'Could not find image {1}: {2}\n'.format( vm_['name'], vm_['image'], exc)) try: kwargs['flavor_id'] = get_size(conn, vm_) except Exception as exc: raise SaltCloudSystemExit('Error creating {0} on OPENSTACK\n\n' 'Could not find size {1}: {2}\n'.format( vm_['name'], vm_['size'], exc)) kwargs['key_name'] = config.get_cloud_config_value('ssh_key_name', vm_, __opts__, search_global=False) security_groups = config.get_cloud_config_value('security_groups', vm_, __opts__, search_global=False) if security_groups is not None: vm_groups = security_groups avail_groups = conn.secgroup_list() group_list = [] for vmg in vm_groups: if vmg in [name for name, details in six.iteritems(avail_groups)]: group_list.append(vmg) else: raise SaltCloudNotFound( 'No such security group: \'{0}\''.format(vmg)) kwargs['security_groups'] = group_list avz = config.get_cloud_config_value('availability_zone', vm_, __opts__, default=None, search_global=False) if avz is not None: kwargs['availability_zone'] = avz kwargs['nics'] = config.get_cloud_config_value('networks', vm_, __opts__, search_global=False, default=None) files = config.get_cloud_config_value('files', vm_, __opts__, search_global=False) if files: kwargs['files'] = {} for src_path in files: if os.path.exists(files[src_path]): with salt.utils.files.fopen(files[src_path], 'r') as fp_: kwargs['files'][src_path] = fp_.read() else: kwargs['files'][src_path] = files[src_path] userdata_file = config.get_cloud_config_value('userdata_file', vm_, __opts__, search_global=False, default=None) if userdata_file is not None: try: with salt.utils.files.fopen(userdata_file, 'r') as fp_: kwargs['userdata'] = salt.utils.cloud.userdata_template( __opts__, vm_, fp_.read()) except Exception as exc: log.exception('Failed to read userdata from %s: %s', userdata_file, exc) kwargs['config_drive'] = config.get_cloud_config_value('config_drive', vm_, __opts__, search_global=False) kwargs.update(get_block_mapping_opts(vm_)) event_kwargs = { 'name': kwargs['name'], 'image': kwargs.get('image_id', 'Boot From Volume'), 'size': kwargs['flavor_id'], } __utils__['cloud.fire_event']( 'event', 'requesting instance', 'salt/cloud/{0}/requesting'.format(vm_['name']), args={ 'kwargs': __utils__['cloud.filter_event']('requesting', event_kwargs, list(event_kwargs)), }, sock_dir=__opts__['sock_dir'], transport=__opts__['transport']) try: data = conn.boot(**kwargs) except Exception as exc: raise SaltCloudSystemExit( 'Error creating {0} on Nova\n\n' 'The following exception was thrown by libcloud when trying to ' 'run the initial deployment: {1}\n'.format(vm_['name'], exc)) if data.extra.get('password', None) is None and vm_.get( 'key_filename', None) is None: raise SaltCloudSystemExit('No password returned. Set ssh_key_file.') floating_ip_conf = config.get_cloud_config_value('floating_ip', vm_, __opts__, search_global=False, default={}) if floating_ip_conf.get('auto_assign', False): floating_ip = None if floating_ip_conf.get('ip_address', None) is not None: ip_address = floating_ip_conf.get('ip_address', None) try: fl_ip_dict = conn.floating_ip_show(ip_address) floating_ip = fl_ip_dict['ip'] except Exception as err: raise SaltCloudSystemExit( 'Error assigning floating_ip for {0} on Nova\n\n' 'The following exception was thrown by libcloud when trying to ' 'assign a floating ip: {1}\n'.format(vm_['name'], err)) else: pool = floating_ip_conf.get('pool', 'public') try: floating_ip = conn.floating_ip_create(pool)['ip'] except Exception: log.info( 'A new IP address was unable to be allocated. ' 'An IP address will be pulled from the already allocated list, ' 'This will cause a race condition when building in parallel.' ) for fl_ip, opts in six.iteritems(conn.floating_ip_list()): if opts['fixed_ip'] is None and opts['pool'] == pool: floating_ip = fl_ip break if floating_ip is None: log.error( 'No IP addresses available to allocate for this server: %s', vm_['name']) def __query_node_data(vm_): try: node = show_instance(vm_['name'], 'action') log.debug('Loaded node data for %s:\n%s', vm_['name'], pprint.pformat(node)) except Exception as err: log.error( 'Failed to get nodes list: %s', err, # Show the traceback if the debug logging level is enabled exc_info_on_loglevel=logging.DEBUG) # Trigger a failure in the wait for IP function return False return node['state'] == 'ACTIVE' or None # if we associate the floating ip here,then we will fail. # As if we attempt to associate a floating IP before the Nova instance has completed building, # it will fail.So we should associate it after the Nova instance has completed building. try: salt.utils.cloud.wait_for_ip(__query_node_data, update_args=(vm_, )) except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc: try: # It might be already up, let's destroy it! destroy(vm_['name']) except SaltCloudSystemExit: pass finally: raise SaltCloudSystemExit(six.text_type(exc)) try: conn.floating_ip_associate(vm_['name'], floating_ip) vm_['floating_ip'] = floating_ip except Exception as exc: raise SaltCloudSystemExit( 'Error assigning floating_ip for {0} on Nova\n\n' 'The following exception was thrown by libcloud when trying to ' 'assign a floating ip: {1}\n'.format(vm_['name'], exc)) if not vm_.get('password', None): vm_['password'] = data.extra.get('password', '') return data, vm_
def create(vm_): ''' Create a single VM from a data dict ''' try: # Check for required profile parameters before sending any API calls. if vm_['profile'] and config.is_profile_configured( __opts__, __active_provider_name__ or 'dimensiondata', vm_['profile']) is False: return False except AttributeError: pass __utils__['cloud.fire_event']( 'event', 'starting create', 'salt/cloud/{0}/creating'.format(vm_['name']), args=__utils__['cloud.filter_event']('creating', vm_, ['name', 'profile', 'provider', 'driver']), sock_dir=__opts__['sock_dir'], transport=__opts__['transport'] ) log.info('Creating Cloud VM %s', vm_['name']) conn = get_conn() rootPw = NodeAuthPassword(vm_['auth']) location = conn.ex_get_location_by_id(vm_['location']) images = conn.list_images(location=location) image = [x for x in images if x.id == vm_['image']][0] network_domains = conn.ex_list_network_domains(location=location) try: network_domain = [y for y in network_domains if y.name == vm_['network_domain']][0] except IndexError: network_domain = conn.ex_create_network_domain( location=location, name=vm_['network_domain'], plan='ADVANCED', description='' ) try: vlan = [y for y in conn.ex_list_vlans( location=location, network_domain=network_domain) if y.name == vm_['vlan']][0] except (IndexError, KeyError): # Use the first VLAN in the network domain vlan = conn.ex_list_vlans( location=location, network_domain=network_domain)[0] kwargs = { 'name': vm_['name'], 'image': image, 'auth': rootPw, 'ex_description': vm_['description'], 'ex_network_domain': network_domain, 'ex_vlan': vlan, 'ex_is_started': vm_['is_started'] } event_data = kwargs.copy() del event_data['auth'] __utils__['cloud.fire_event']( 'event', 'requesting instance', 'salt/cloud/{0}/requesting'.format(vm_['name']), args=__utils__['cloud.filter_event']('requesting', event_data, list(event_data)), sock_dir=__opts__['sock_dir'], transport=__opts__['transport'] ) try: data = conn.create_node(**kwargs) except Exception as exc: log.error( 'Error creating %s on DIMENSIONDATA\n\n' 'The following exception was thrown by libcloud when trying to ' 'run the initial deployment: \n%s', vm_['name'], exc, exc_info_on_loglevel=logging.DEBUG ) return False try: data = salt.utils.cloud.wait_for_ip( _query_node_data, update_args=(vm_, data), timeout=config.get_cloud_config_value( 'wait_for_ip_timeout', vm_, __opts__, default=25 * 60), interval=config.get_cloud_config_value( 'wait_for_ip_interval', vm_, __opts__, default=30), max_failures=config.get_cloud_config_value( 'wait_for_ip_max_failures', vm_, __opts__, default=60), ) except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc: try: # It might be already up, let's destroy it! destroy(vm_['name']) except SaltCloudSystemExit: pass finally: raise SaltCloudSystemExit(six.text_type(exc)) log.debug('VM is now running') if ssh_interface(vm_) == 'private_ips': ip_address = preferred_ip(vm_, data.private_ips) else: ip_address = preferred_ip(vm_, data.public_ips) log.debug('Using IP address %s', ip_address) if salt.utils.cloud.get_salt_interface(vm_, __opts__) == 'private_ips': salt_ip_address = preferred_ip(vm_, data.private_ips) log.info('Salt interface set to: %s', salt_ip_address) else: salt_ip_address = preferred_ip(vm_, data.public_ips) log.debug('Salt interface set to: %s', salt_ip_address) if not ip_address: raise SaltCloudSystemExit( 'No IP addresses could be found.' ) vm_['salt_host'] = salt_ip_address vm_['ssh_host'] = ip_address vm_['password'] = vm_['auth'] ret = salt.utils.cloud.bootstrap(vm_, __opts__) ret.update(data.__dict__) if 'password' in data.extra: del data.extra['password'] log.info('Created Cloud VM \'%s\'', vm_['name']) log.debug( '\'%s\' VM creation details:\n%s', vm_['name'], pprint.pformat(data.__dict__) ) __utils__['cloud.fire_event']( 'event', 'created instance', 'salt/cloud/{0}/created'.format(vm_['name']), args=__utils__['cloud.filter_event']('created', vm_, ['name', 'profile', 'provider', 'driver']), sock_dir=__opts__['sock_dir'], transport=__opts__['transport'] ) return ret
def create(vm_): ''' Create a single VM from a data dict ''' try: # Check for required profile parameters before sending any API calls. if vm_['profile'] and config.is_profile_configured( __opts__, __active_provider_name__ or 'nova', vm_['profile'], vm_=vm_) is False: return False except AttributeError: pass deploy = config.get_cloud_config_value('deploy', vm_, __opts__) key_filename = config.get_cloud_config_value('ssh_key_file', vm_, __opts__, search_global=False, default=None) if key_filename is not None and not os.path.isfile(key_filename): raise SaltCloudConfigError( 'The defined ssh_key_file \'{0}\' does not exist'.format( key_filename)) vm_['key_filename'] = key_filename __utils__['cloud.fire_event']( 'event', 'starting create', 'salt/cloud/{0}/creating'.format(vm_['name']), args=__utils__['cloud.filter_event']( 'creating', vm_, ['name', 'profile', 'provider', 'driver']), sock_dir=__opts__['sock_dir'], transport=__opts__['transport']) conn = get_conn() if 'instance_id' in vm_: # This was probably created via another process, and doesn't have # things like salt keys created yet, so let's create them now. if 'pub_key' not in vm_ and 'priv_key' not in vm_: log.debug('Generating minion keys for \'%s\'', vm_['name']) vm_['priv_key'], vm_['pub_key'] = salt.utils.cloud.gen_keys( salt.config.get_cloud_config_value('keysize', vm_, __opts__)) data = conn.server_show_libcloud(vm_['instance_id']) if vm_['key_filename'] is None and 'change_password' in __opts__ and __opts__[ 'change_password'] is True: vm_['password'] = salt.utils.pycrypto.secure_password() conn.root_password(vm_['instance_id'], vm_['password']) else: # Put together all of the information required to request the instance, # and then fire off the request for it data, vm_ = request_instance(vm_) # Pull the instance ID, valid for both spot and normal instances vm_['instance_id'] = data.id try: data = salt.utils.cloud.wait_for_ip( _query_node_data, update_args=(vm_, data, conn), timeout=config.get_cloud_config_value('wait_for_ip_timeout', vm_, __opts__, default=10 * 60), interval=config.get_cloud_config_value('wait_for_ip_interval', vm_, __opts__, default=10), ) except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc: try: # It might be already up, let's destroy it! destroy(vm_['name']) except SaltCloudSystemExit: pass finally: raise SaltCloudSystemExit(six.text_type(exc)) log.debug('VM is now running') if ssh_interface(vm_) == 'private_ips': ip_address = preferred_ip(vm_, data.private_ips) elif ssh_interface(vm_) == 'fixed_ips': ip_address = preferred_ip(vm_, data.fixed_ips) elif ssh_interface(vm_) == 'floating_ips': ip_address = preferred_ip(vm_, data.floating_ips) else: ip_address = preferred_ip(vm_, data.public_ips) log.debug('Using IP address %s', ip_address) if salt.utils.cloud.get_salt_interface(vm_, __opts__) == 'private_ips': salt_ip_address = preferred_ip(vm_, data.private_ips) log.info('Salt interface set to: %s', salt_ip_address) elif salt.utils.cloud.get_salt_interface(vm_, __opts__) == 'fixed_ips': salt_ip_address = preferred_ip(vm_, data.fixed_ips) log.info('Salt interface set to: %s', salt_ip_address) elif salt.utils.cloud.get_salt_interface(vm_, __opts__) == 'floating_ips': salt_ip_address = preferred_ip(vm_, data.floating_ips) log.info('Salt interface set to: %s', salt_ip_address) else: salt_ip_address = preferred_ip(vm_, data.public_ips) log.debug('Salt interface set to: %s', salt_ip_address) if not ip_address: raise SaltCloudSystemExit('A valid IP address was not found') vm_['ssh_host'] = ip_address vm_['salt_host'] = salt_ip_address ret = __utils__['cloud.bootstrap'](vm_, __opts__) ret.update(data.__dict__) if 'password' in ret['extra']: del ret['extra']['password'] log.info('Created Cloud VM \'%s\'', vm_['name']) log.debug('\'%s\' VM creation details:\n%s', vm_['name'], pprint.pformat(data.__dict__)) event_data = { 'name': vm_['name'], 'profile': vm_['profile'], 'provider': vm_['driver'], 'instance_id': vm_['instance_id'], 'floating_ips': data.floating_ips, 'fixed_ips': data.fixed_ips, 'private_ips': data.private_ips, 'public_ips': data.public_ips } __utils__['cloud.fire_event']('event', 'created instance', 'salt/cloud/{0}/created'.format(vm_['name']), args=__utils__['cloud.filter_event']( 'created', event_data, list(event_data)), sock_dir=__opts__['sock_dir'], transport=__opts__['transport']) __utils__['cloud.cachedir_index_add'](vm_['name'], vm_['profile'], 'nova', vm_['driver']) return ret
def create(vm_): ''' Create a single VM from a data dict ''' key_filename = config.get_cloud_config_value('private_key', vm_, __opts__, search_global=False, default=None) if key_filename is not None and not os.path.isfile(key_filename): raise SaltCloudConfigError( 'The defined key_filename {0!r} does not exist'.format( key_filename)) location = get_location(vm_) log.info('Creating Cloud VM {0} in {1}'.format(vm_['name'], location)) conn = get_conn(location=location) usernames = ssh_username(vm_) kwargs = { 'ssh_key': config.get_cloud_config_value('private_key', vm_, __opts__, search_global=False), 'name': vm_['name'], 'image': get_image(conn, vm_), 'size': get_size(conn, vm_), 'location': get_availability_zone(conn, vm_) } ex_keyname = keyname(vm_) if ex_keyname: kwargs['ex_keyname'] = ex_keyname ex_securitygroup = securitygroup(vm_) if ex_securitygroup: kwargs['ex_securitygroup'] = ex_securitygroup ex_blockdevicemappings = block_device_mappings(vm_) if ex_blockdevicemappings: kwargs['ex_blockdevicemappings'] = ex_blockdevicemappings ex_iam_profile = iam_profile(vm_) if ex_iam_profile: # libcloud does not implement 'iam_profile' yet. # A pull request has been suggested # https://github.com/apache/libcloud/pull/150 raise SaltCloudConfigError( 'libcloud does not implement \'iam_profile\' yet. ' 'Use EC2 driver instead.') tags = config.get_cloud_config_value('tag', vm_, __opts__, {}, search_global=False) if not isinstance(tags, dict): raise SaltCloudConfigError('\'tag\' should be a dict.') kwargs['ex_metadata'] = config.get_cloud_config_value('metadata', vm_, __opts__, default={}, search_global=False) if not isinstance(kwargs['ex_metadata'], dict): raise SaltCloudConfigError('\'metadata\' should be a dict.') try: data = conn.create_node(**kwargs) except Exception as exc: log.error( 'Error creating {0} on AWS\n\n' 'The following exception was thrown by libcloud when trying to ' 'run the initial deployment: {1}\n'.format(vm_['name'], exc), # Show the traceback if the debug logging level is enabled exc_info_on_loglevel=logging.DEBUG) return False log.info('Created node {0}'.format(vm_['name'])) def __get_node_data(conn, vm_name): data = get_node(conn, vm_name) if data is None: # Trigger a failure in the waiting function return False if ssh_interface(vm_) == 'private_ips' and data.private_ips: return data if ssh_interface(vm_) == 'public_ips' and data.public_ips: return data try: data = salt.utils.cloud.wait_for_ip( __get_node_data, update_args=(conn, vm_['name']), timeout=config.get_cloud_config_value('wait_for_ip_timeout', vm_, __opts__, default=5 * 60), interval=config.get_cloud_config_value('wait_for_ip_interval', vm_, __opts__, default=0.5), ) except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc: try: # It might be already up, let's destroy it! destroy(vm_['name']) except SaltCloudSystemExit: pass finally: raise SaltCloudSystemExit(str(exc)) if tags: set_tags(vm_['name'], tags, call='action') if ssh_interface(vm_) == 'private_ips': log.info('Salt node data. Private_ip: {0}'.format(data.private_ips[0])) ip_address = data.private_ips[0] else: log.info('Salt node data. Public_ip: {0}'.format(data.public_ips[0])) ip_address = data.public_ips[0] if salt.utils.cloud.get_salt_interface(vm_, __opts__) == 'private_ips': salt_ip_address = data.private_ips[0] log.info('Salt interface set to: {0}'.format(salt_ip_address)) else: salt_ip_address = data.public_ips[0] log.debug('Salt interface set to: {0}'.format(salt_ip_address)) username = '******' ssh_connect_timeout = config.get_cloud_config_value( 'ssh_connect_timeout', vm_, __opts__, 900 # 15 minutes ) ssh_port = config.get_cloud_config_value('ssh_port', vm_, __opts__, 22) if salt.utils.cloud.wait_for_port(ip_address, timeout=ssh_connect_timeout): for user in usernames: if salt.utils.cloud.wait_for_passwd( host=ip_address, username=user, ssh_timeout=config.get_cloud_config_value( 'wait_for_passwd_timeout', vm_, __opts__, default=1 * 60), key_filename=key_filename, known_hosts_file=config.get_cloud_config_value( 'known_hosts_file', vm_, __opts__, default='/dev/null'), ): username = user break else: raise SaltCloudSystemExit( 'Failed to authenticate against remote ssh') ret = {} if config.get_cloud_config_value('deploy', vm_, __opts__) is True: deploy_script = script(vm_) deploy_kwargs = { 'opts': __opts__, 'host': ip_address, 'port': ssh_port, 'salt_host': salt_ip_address, 'username': username, 'key_filename': key_filename, 'tmp_dir': config.get_cloud_config_value('tmp_dir', vm_, __opts__, default='/tmp/.saltcloud'), 'deploy_command': config.get_cloud_config_value( 'deploy_command', vm_, __opts__, default='/tmp/.saltcloud/deploy.sh', ), 'tty': config.get_cloud_config_value('tty', vm_, __opts__, default=True), 'script': deploy_script.script, 'name': vm_['name'], 'sudo': config.get_cloud_config_value('sudo', vm_, __opts__, default=(username != 'root')), 'sudo_password': config.get_cloud_config_value('sudo_password', vm_, __opts__, default=None), 'start_action': __opts__['start_action'], 'parallel': __opts__['parallel'], 'conf_file': __opts__['conf_file'], 'sock_dir': __opts__['sock_dir'], 'minion_pem': vm_['priv_key'], 'minion_pub': vm_['pub_key'], 'keep_tmp': __opts__['keep_tmp'], 'preseed_minion_keys': vm_.get('preseed_minion_keys', None), 'display_ssh_output': config.get_cloud_config_value('display_ssh_output', vm_, __opts__, default=True), 'script_args': config.get_cloud_config_value('script_args', vm_, __opts__), 'script_env': config.get_cloud_config_value('script_env', vm_, __opts__), 'minion_conf': salt.utils.cloud.minion_config(__opts__, vm_) } # Deploy salt-master files, if necessary if config.get_cloud_config_value('make_master', vm_, __opts__) is True: deploy_kwargs['make_master'] = True deploy_kwargs['master_pub'] = vm_['master_pub'] deploy_kwargs['master_pem'] = vm_['master_pem'] master_conf = salt.utils.cloud.master_config(__opts__, vm_) deploy_kwargs['master_conf'] = master_conf if master_conf.get('syndic_master', None): deploy_kwargs['make_syndic'] = True deploy_kwargs['make_minion'] = config.get_cloud_config_value( 'make_minion', vm_, __opts__, default=True) # Check for Windows install params win_installer = config.get_cloud_config_value('win_installer', vm_, __opts__) if win_installer: deploy_kwargs['win_installer'] = win_installer minion = salt.utils.cloud.minion_config(__opts__, vm_) deploy_kwargs['master'] = minion['master'] deploy_kwargs['username'] = config.get_cloud_config_value( 'win_username', vm_, __opts__, default='Administrator') deploy_kwargs['password'] = config.get_cloud_config_value( 'win_password', vm_, __opts__, default='') # Store what was used to the deploy the VM ret['deploy_kwargs'] = deploy_kwargs deployed = False if win_installer: deployed = salt.utils.cloud.deploy_windows(**deploy_kwargs) else: deployed = salt.utils.cloud.deploy_script(**deploy_kwargs) if deployed: log.info('Salt installed on {name}'.format(**vm_)) else: log.error('Failed to start Salt on Cloud VM {name}'.format(**vm_)) ret.update(data.__dict__) log.info('Created Cloud VM {0[name]!r}'.format(vm_)) log.debug('{0[name]!r} VM creation details:\n{1}'.format( vm_, pprint.pformat(data.__dict__))) volumes = config.get_cloud_config_value('volumes', vm_, __opts__, search_global=True) if volumes: log.info('Create and attach volumes to node {0}'.format(data.name)) create_attach_volumes(volumes, location, data) return ret
def destroy(name, conn=None, call=None): ''' Delete a single VM ''' if call == 'function': raise SaltCloudSystemExit( 'The destroy action must be called with -d, --destroy, ' '-a or --action.' ) __utils__['cloud.fire_event']( 'event', 'destroying instance', 'salt/cloud/{0}/destroying'.format(name), args={'name': name}, sock_dir=__opts__['sock_dir'], transport=__opts__['transport'] ) if not conn: conn = get_conn() # pylint: disable=E0602 node = get_node(conn, name) profiles = get_configured_provider()['profiles'] # pylint: disable=E0602 if node is None: log.error('Unable to find the VM {0}'.format(name)) profile = None if 'metadata' in node.extra and 'profile' in node.extra['metadata']: profile = node.extra['metadata']['profile'] flush_mine_on_destroy = False if profile and profile in profiles and 'flush_mine_on_destroy' in profiles[profile]: flush_mine_on_destroy = profiles[profile]['flush_mine_on_destroy'] if flush_mine_on_destroy: log.info('Clearing Salt Mine: {0}'.format(name)) mopts_ = salt.config.DEFAULT_MINION_OPTS conf_path = '/'.join(__opts__['conf_file'].split('/')[:-1]) mopts_.update( salt.config.minion_config(os.path.join(conf_path, 'minion')) ) client = salt.client.get_local_client(mopts_) minions = client.cmd(name, 'mine.flush') log.info('Clearing Salt Mine: {0}, {1}'.format(name, flush_mine_on_destroy)) log.info('Destroying VM: {0}'.format(name)) ret = conn.destroy_node(node) if ret: log.info('Destroyed VM: {0}'.format(name)) # Fire destroy action __utils__['cloud.fire_event']( 'event', 'destroyed instance', 'salt/cloud/{0}/destroyed'.format(name), args={'name': name}, sock_dir=__opts__['sock_dir'], transport=__opts__['transport'] ) if __opts__['delete_sshkeys'] is True: public_ips = getattr(node, __opts__.get('ssh_interface', 'public_ips')) if public_ips: salt.utils.cloud.remove_sshkey(public_ips[0]) private_ips = getattr(node, __opts__.get('ssh_interface', 'private_ips')) if private_ips: salt.utils.cloud.remove_sshkey(private_ips[0]) if __opts__.get('update_cachedir', False) is True: __utils__['cloud.delete_minion_cachedir'](name, __active_provider_name__.split(':')[0], __opts__) return True log.error('Failed to Destroy VM: {0}'.format(name)) return False
def get_entry(dict_, key, value): for entry in dict_: if entry[key] == value: return entry raise SaltCloudSystemExit('Unable to find {0} in {1}.'.format(key, dict_))
def destroy(name, conn=None, call=None, kwargs=None): # pylint: disable=unused-argument ''' Destroy a VM CLI Examples: .. code-block:: bash salt-cloud -d myminion salt-cloud -a destroy myminion service_name=myservice ''' if call == 'function': raise SaltCloudSystemExit( 'The destroy action must be called with -d, --destroy, ' '-a or --action.') global compconn # pylint: disable=global-statement,invalid-name if not compconn: compconn = get_conn() if kwargs is None: kwargs = {} node_data = show_instance(name, call='action') vhd = node_data['storage_profile']['os_disk']['vhd']['uri'] ret = {} log.debug('Deleting VM') result = compconn.virtual_machines.delete(node_data['resource_group'], name) result.wait() if __opts__.get('update_cachedir', False) is True: salt.utils.cloud.delete_minion_cachedir( name, __active_provider_name__.split(':')[0], __opts__) cleanup_disks = config.get_cloud_config_value( 'cleanup_disks', get_configured_provider(), __opts__, search_global=False, default=False, ) if cleanup_disks: cleanup_vhds = kwargs.get( 'delete_vhd', config.get_cloud_config_value( 'cleanup_vhds', get_configured_provider(), __opts__, search_global=False, default=False, )) if cleanup_vhds: log.debug('Deleting vhd') comps = vhd.split('.') container = comps[0].replace('https://', '') blob = node_data['storage_profile']['os_disk']['name'] ret[name]['delete_disk'] = { 'delete_vhd': cleanup_vhds, 'container': container, 'blob': blob, } #data = delete_disk(kwargs={'name': disk_name, 'delete_vhd': cleanup_vhds}, call='function') return ret
def query(method='servers', server_id=None, command=None, args=None, http_method='GET', root='api_root'): ''' Make a call to the Scaleway API. ''' if root == 'api_root': default_url = 'https://cp-par1.scaleway.com' else: default_url = 'https://api-marketplace.scaleway.com' base_path = six.text_type( config.get_cloud_config_value(root, get_configured_provider(), __opts__, search_global=False, default=default_url)) path = '{0}/{1}/'.format(base_path, method) if server_id: path += '{0}/'.format(server_id) if command: path += command if not isinstance(args, dict): args = {} token = config.get_cloud_config_value('token', get_configured_provider(), __opts__, search_global=False) data = salt.utils.json.dumps(args) request = __utils__["http.query"](path, method=http_method, data=data, status=True, decode=True, decode_type='json', data_render=True, data_renderer='json', headers=True, header_dict={ 'X-Auth-Token': token, 'User-Agent': "salt-cloud", 'Content-Type': 'application/json' }) if request['status'] > 299: raise SaltCloudSystemExit( 'An error occurred while querying Scaleway. HTTP Code: {0} ' 'Error: \'{1}\''.format(request['status'], request['error'])) # success without data if request['status'] == 204: return True return salt.utils.json.loads(request['body'])
def avail_images(conn=None, call=None): # pylint: disable=unused-argument ''' List available images for Azure ''' if call == 'action': raise SaltCloudSystemExit( 'The avail_images function must be called with ' '-f or --function, or with the --list-images option') global compconn # pylint: disable=global-statement,invalid-name if not compconn: compconn = get_conn() region = get_location() bank = 'cloud/metadata/azurearm/{0}'.format(region) publishers = _cache( bank, 'publishers', compconn.virtual_machine_images.list_publishers, location=region, ) ret = {} for publisher in publishers: pub_bank = os.path.join(bank, 'publishers', publisher) offers = _cache( pub_bank, 'offers', compconn.virtual_machine_images.list_offers, location=region, publisher_name=publishers[publisher]['name'], ) for offer in offers: offer_bank = os.path.join(pub_bank, 'offers', offer) skus = _cache( offer_bank, 'skus', compconn.virtual_machine_images.list_skus, location=region, publisher_name=publishers[publisher]['name'], offer=offers[offer]['name'], ) for sku in skus: sku_bank = os.path.join(offer_bank, 'skus', sku) results = _cache( sku_bank, 'results', compconn.virtual_machine_images.list, location=region, publisher_name=publishers[publisher]['name'], offer=offers[offer]['name'], skus=skus[sku]['name'], ) for version in results: name = '|'.join(( publishers[publisher]['name'], offers[offer]['name'], skus[sku]['name'], results[version]['name'], )) ret[name] = { 'publisher': publishers[publisher]['name'], 'offer': offers[offer]['name'], 'sku': skus[sku]['name'], 'version': results[version]['name'], } return ret
def query( method="servers", server_id=None, command=None, args=None, http_method="GET", root="api_root", ): """ Make a call to the Scaleway API. """ if root == "api_root": default_url = "https://cp-par1.scaleway.com" else: default_url = "https://api-marketplace.scaleway.com" vm_ = get_configured_provider() base_path = str( config.get_cloud_config_value( root, vm_, __opts__, search_global=False, default=default_url, )) path = "{}/{}/".format(base_path, method) if server_id: path += "{}/".format(server_id) if command: path += command if not isinstance(args, dict): args = {} token = config.get_cloud_config_value("token", vm_, __opts__, search_global=False) data = salt.utils.json.dumps(args) request = __utils__["http.query"]( path, method=http_method, data=data, headers={ "X-Auth-Token": token, "User-Agent": "salt-cloud", "Content-Type": "application/json", }, ) if request.status_code > 299: raise SaltCloudSystemExit( "An error occurred while querying Scaleway. HTTP Code: {} " "Error: '{}'".format(request.status_code, request.text)) # success without data if request["status"] == 204: return True return salt.utils.json.loads(request["body"])
def query(params=None): ''' Make a web call to QingCloud IaaS API. ''' path = 'https://api.qingcloud.com/iaas/' access_key_id = config.get_cloud_config_value('access_key_id', get_configured_provider(), __opts__, search_global=False) access_key_secret = config.get_cloud_config_value( 'secret_access_key', get_configured_provider(), __opts__, search_global=False) # public interface parameters real_parameters = { 'access_key_id': access_key_id, 'signature_version': DEFAULT_QINGCLOUD_SIGNATURE_VERSION, 'time_stamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), 'version': DEFAULT_QINGCLOUD_API_VERSION, } # include action or function parameters if params: for key, value in params.items(): if isinstance(value, list): for i in range(1, len(value) + 1): if isinstance(value[i - 1], dict): for sk, sv in value[i - 1].items(): if isinstance(sv, dict) or isinstance(sv, list): # sv = json_dump(sv) sv = json.dumps(sv, separators=(',', ':')) real_parameters['{0}.{1}.{2}'.format(key, i, sk)] = sv else: real_parameters['{0}.{1}'.format(key, i)] = value[i - 1] else: real_parameters[key] = value # Calculate the string for Signature signature = _compute_signature(real_parameters, access_key_secret, 'GET', '/iaas/') real_parameters['signature'] = signature # print('parameters:') # pprint.pprint(real_parameters) request = requests.get(path, params=real_parameters, verify=False) # print('url:') # print(request.url) if request.status_code != 200: raise SaltCloudSystemExit( 'An error occurred while querying QingCloud. HTTP Code: {0} ' 'Error: {1!r}'.format(request.status_code, request.text)) log.debug(request.url) content = request.text result = json.loads(content, object_hook=salt.utils.decode_dict) # print('response:') # pprint.pprint(result) if result['ret_code'] != 0: raise SaltCloudSystemExit(pprint.pformat(result.get('message', {}))) return result
def __init__(self, username, project_id, auth_url, region_name=None, password=None, os_auth_plugin=None, **kwargs): ''' Set up nova credentials ''' self.kwargs = kwargs.copy() if not self.extensions: if hasattr(OpenStackComputeShell, '_discover_extensions'): self.extensions = OpenStackComputeShell()._discover_extensions( '2.0') else: self.extensions = client.discover_extensions('2.0') for extension in self.extensions: extension.run_hooks('__pre_parse_args__') self.kwargs['extensions'] = self.extensions self.kwargs['username'] = username self.kwargs['project_id'] = project_id self.kwargs['auth_url'] = auth_url self.kwargs['region_name'] = region_name self.kwargs['service_type'] = 'compute' # used in novaclient extensions to see if they are rackspace or not, to know if it needs to load # the hooks for that extension or not. This is cleaned up by sanatize_novaclient self.kwargs['os_auth_url'] = auth_url if os_auth_plugin is not None: novaclient.auth_plugin.discover_auth_systems() auth_plugin = novaclient.auth_plugin.load_plugin(os_auth_plugin) self.kwargs['auth_plugin'] = auth_plugin self.kwargs['auth_system'] = os_auth_plugin if not self.kwargs.get('api_key', None): self.kwargs['api_key'] = password # This has to be run before sanatize_novaclient before extra variables are cleaned out. if hasattr(self, 'extensions'): # needs an object, not a dictionary self.kwargstruct = KwargsStruct(**self.kwargs) for extension in self.extensions: extension.run_hooks('__post_parse_args__', self.kwargstruct) self.kwargs = self.kwargstruct.__dict__ self.kwargs = sanatize_novaclient(self.kwargs) # Requires novaclient version >= 2.6.1 self.kwargs['version'] = str(kwargs.get('version', 2)) conn = client.Client(**self.kwargs) try: conn.client.authenticate() except novaclient.exceptions.AmbiguousEndpoints: raise SaltCloudSystemExit( "Nova provider requires a 'region_name' to be specified") self.kwargs['auth_token'] = conn.client.auth_token self.catalog = conn.client.service_catalog.catalog['access'][ 'serviceCatalog'] if region_name is not None: servers_endpoints = get_entry(self.catalog, 'type', 'compute')['endpoints'] self.kwargs['bypass_url'] = get_entry(servers_endpoints, 'region', region_name)['publicURL'] self.compute_conn = client.Client(**self.kwargs) volume_endpoints = get_entry(self.catalog, 'type', 'volume', raise_error=False).get('endpoints', {}) if volume_endpoints: if region_name is not None: self.kwargs['bypass_url'] = get_entry(volume_endpoints, 'region', region_name)['publicURL'] self.volume_conn = client.Client(**self.kwargs) if hasattr(self, 'extensions'): self.expand_extensions() else: self.volume_conn = None
def create(vm_): ''' Create a single instance from a data dict. CLI Examples: .. code-block:: bash salt-cloud -p qingcloud-ubuntu-c1m1 hostname1 salt-cloud -m /path/to/mymap.sls -P ''' try: # Check for required profile parameters before sending any API calls. if config.is_profile_configured( __opts__, __active_provider_name__ or 'qingcloud', vm_['profile']) is False: return False except AttributeError: pass # Since using "provider: <provider-engine>" is deprecated, alias provider # to use driver: "driver: <provider-engine>" if 'provider' in vm_: vm_['driver'] = vm_.pop('provider') salt.utils.cloud.fire_event('event', 'starting create', 'salt/cloud/{0}/creating'.format(vm_['name']), { 'name': vm_['name'], 'profile': vm_['profile'], 'provider': vm_['driver'], }, transport=__opts__['transport']) log.info('Creating Cloud VM {0}'.format(vm_['name'])) # params params = { 'action': 'RunInstances', 'instance_name': vm_['name'], 'zone': _get_location(vm_), 'instance_type': _get_size(vm_), 'image_id': _get_image(vm_), 'vxnets.1': vm_['vxnets'], 'login_mode': vm_['login_mode'], 'login_keypair': vm_['login_keypair'], } salt.utils.cloud.fire_event('event', 'requesting instance', 'salt/cloud/{0}/requesting'.format( vm_['name']), {'kwargs': params}, transport=__opts__['transport']) result = query(params) new_instance_id = result['instances'][0] try: data = salt.utils.cloud.wait_for_ip( _query_node_data, update_args=(new_instance_id, ), timeout=config.get_cloud_config_value('wait_for_ip_timeout', vm_, __opts__, default=10 * 60), interval=config.get_cloud_config_value('wait_for_ip_interval', vm_, __opts__, default=10), ) except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc: try: # It might be already up, let's destroy it! destroy(vm_['name']) except SaltCloudSystemExit: pass finally: raise SaltCloudSystemExit(str(exc)) private_ip = data['private_ips'][0] log.debug('VM {0} is now running'.format(private_ip)) vm_['ssh_host'] = private_ip # The instance is booted and accessible, let's Salt it! salt.utils.cloud.bootstrap(vm_, __opts__) log.info('Created Cloud VM {0[name]!r}'.format(vm_)) log.debug('{0[name]!r} VM creation details:\n{1}'.format( vm_, pprint.pformat(data))) salt.utils.cloud.fire_event('event', 'created instance', 'salt/cloud/{0}/created'.format(vm_['name']), { 'name': vm_['name'], 'profile': vm_['profile'], 'provider': vm_['driver'], }, transport=__opts__['transport']) return data
def create(vm_): ''' Create a single VM from a data dict ''' try: # Check for required profile parameters before sending any API calls. if vm_['profile'] and config.is_profile_configured(__opts__, __active_provider_name__ or 'digital_ocean', vm_['profile']) is False: return False except AttributeError: pass # Since using "provider: <provider-engine>" is deprecated, alias provider # to use driver: "driver: <provider-engine>" if 'provider' in vm_: vm_['driver'] = vm_.pop('provider') salt.utils.cloud.fire_event( 'event', 'starting create', 'salt/cloud/{0}/creating'.format(vm_['name']), { 'name': vm_['name'], 'profile': vm_['profile'], 'provider': vm_['driver'], }, transport=__opts__['transport'] ) log.info('Creating Cloud VM {0}'.format(vm_['name'])) kwargs = { 'name': vm_['name'], 'size': get_size(vm_), 'image': get_image(vm_), 'region': get_location(vm_), 'ssh_keys': [] } # backwards compat ssh_key_name = config.get_cloud_config_value( 'ssh_key_name', vm_, __opts__, search_global=False ) if ssh_key_name: kwargs['ssh_keys'].append(get_keyid(ssh_key_name)) ssh_key_names = config.get_cloud_config_value( 'ssh_key_names', vm_, __opts__, search_global=False, default=False ) if ssh_key_names: for key in ssh_key_names.split(','): kwargs['ssh_keys'].append(get_keyid(key)) key_filename = config.get_cloud_config_value( 'ssh_key_file', vm_, __opts__, search_global=False, default=None ) if key_filename is not None and not os.path.isfile(key_filename): raise SaltCloudConfigError( 'The defined key_filename {0!r} does not exist'.format( key_filename ) ) if key_filename is None: raise SaltCloudConfigError( 'The DigitalOcean driver requires an ssh_key_file and an ssh_key_name ' 'because it does not supply a root password upon building the server.' ) private_networking = config.get_cloud_config_value( 'private_networking', vm_, __opts__, search_global=False, default=None, ) if private_networking is not None: if not isinstance(private_networking, bool): raise SaltCloudConfigError("'private_networking' should be a boolean value.") kwargs['private_networking'] = private_networking backups_enabled = config.get_cloud_config_value( 'backups_enabled', vm_, __opts__, search_global=False, default=None, ) if backups_enabled is not None: if not isinstance(backups_enabled, bool): raise SaltCloudConfigError("'backups_enabled' should be a boolean value.") kwargs['backups'] = backups_enabled ipv6 = config.get_cloud_config_value( 'ipv6', vm_, __opts__, search_global=False, default=None, ) if ipv6 is not None: if not isinstance(ipv6, bool): raise SaltCloudConfigError("'ipv6' should be a boolean value.") kwargs['ipv6'] = ipv6 salt.utils.cloud.fire_event( 'event', 'requesting instance', 'salt/cloud/{0}/requesting'.format(vm_['name']), {'kwargs': kwargs}, transport=__opts__['transport'] ) try: ret = create_node(kwargs) except Exception as exc: log.error( 'Error creating {0} on DIGITAL_OCEAN\n\n' 'The following exception was thrown when trying to ' 'run the initial deployment: {1}'.format( vm_['name'], str(exc) ), # Show the traceback if the debug logging level is enabled exc_info_on_loglevel=logging.DEBUG ) return False def __query_node_data(vm_name): data = show_instance(vm_name, 'action') if not data: # Trigger an error in the wait_for_ip function return False if data['networks'].get('v4'): for network in data['networks']['v4']: if network['type'] == 'public': return data return False try: data = salt.utils.cloud.wait_for_ip( __query_node_data, update_args=(vm_['name'],), timeout=config.get_cloud_config_value( 'wait_for_ip_timeout', vm_, __opts__, default=10 * 60), interval=config.get_cloud_config_value( 'wait_for_ip_interval', vm_, __opts__, default=10), ) except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc: try: # It might be already up, let's destroy it! destroy(vm_['name']) except SaltCloudSystemExit: pass finally: raise SaltCloudSystemExit(str(exc)) for network in data['networks']['v4']: if network['type'] == 'public': ip_address = network['ip_address'] vm_['key_filename'] = key_filename vm_['ssh_host'] = ip_address ret = salt.utils.cloud.bootstrap(vm_, __opts__) ret.update(data) log.info('Created Cloud VM {0[name]!r}'.format(vm_)) log.debug( '{0[name]!r} VM creation details:\n{1}'.format( vm_, pprint.pformat(data) ) ) salt.utils.cloud.fire_event( 'event', 'created instance', 'salt/cloud/{0}/created'.format(vm_['name']), { 'name': vm_['name'], 'profile': vm_['profile'], 'provider': vm_['driver'], }, transport=__opts__['transport'] ) return ret
def query(method='droplets', droplet_id=None, command=None, args=None, http_method='get'): ''' Make a web call to DigitalOcean ''' base_path = str( config.get_cloud_config_value( 'api_root', get_configured_provider(), __opts__, search_global=False, default='https://api.digitalocean.com/v2')) path = '{0}/{1}/'.format(base_path, method) if droplet_id: path += '{0}/'.format(droplet_id) if command: path += command if not isinstance(args, dict): args = {} personal_access_token = config.get_cloud_config_value( 'personal_access_token', get_configured_provider(), __opts__, search_global=False) data = json.dumps(args) requester = getattr(requests, http_method) request = requester(path, data=data, headers={ 'Authorization': 'Bearer ' + personal_access_token, 'Content-Type': 'application/json' }) if request.status_code > 299: raise SaltCloudSystemExit( 'An error occurred while querying DigitalOcean. HTTP Code: {0} ' 'Error: \'{1}\''.format( request.status_code, # request.read() request.text)) log.debug(request.url) # success without data if request.status_code == 204: return True content = request.text result = json.loads(content) if result.get('status', '').lower() == 'error': raise SaltCloudSystemExit( pprint.pformat(result.get('error_message', {}))) return result