def __prepare_key_pair(self, key_name, private_key_path, public_key_path, password): if not key_name: log.warn('user_key_name has not been defined, assuming password-based authentication') return if key_name in [k.name for k in self.driver.list_key_pairs()]: log.info('Key pair `%s` already exists, skipping import.', key_name) return if public_key_path: log.debug("importing public key from file %s ...", public_key_path) if not self.driver.import_key_pair_from_file( name=key_name, key_file_path=os.path.expandvars(os.path.expanduser(public_key_path))): raise KeypairError( 'Could not upload public key {p}' .format(p=public_key_path)) elif private_key_path: if not private_key_path.endswith('.pem'): raise KeypairError( 'can only work with .pem private keys,' ' derive public key and set user_key_public') log.debug("deriving and importing public key from private key") self.__import_pem(key_name, private_key_path, password) else: pem_file_path = os.path.join(self.storage_path, key_name + '.pem') if not os.path.exists(pem_file_path): with open(pem_file_path, 'w') as new_key_file: new_key_file.write( self.driver.create_key_pair(name=key_name)) self.__import_pem(key_name, pem_file_path, password)
def __prepare_key_pair(self, key_name, private_key_path, public_key_path, password): if not key_name: log.warn('user_key_name has not been defined, assuming password-based authentication') return if key_name in [k.name for k in self.driver.list_key_pairs()]: log.info('Key pair `%s` already exists, skipping import.', key_name) return if public_key_path: log.debug("importing public key from file %s ...", public_key_path) if not self.driver.import_key_pair_from_file( name=key_name, key_file_path=os.path.expandvars(os.path.expanduser(public_key_path))): raise KeypairError( 'Could not upload public key {p}' .format(p=public_key_path)) elif private_key_path: if not private_key_path.endswith('.pem'): raise KeypairError( 'can only work with .pem private keys,' ' derive public key and set user_key_public') log.debug("deriving and importing public key from private key") self.__import_pem(key_name, private_key_path, password) else: pem_file_path = os.path.join(self.storage_path, key_name + '.pem') if not os.path.exists(pem_file_path): with open(pem_file_path, 'w') as new_key_file: new_key_file.write( self.driver.create_key_pair(name=key_name)) self.__import_pem(key_name, pem_file_path, password)
def __get_instance(self, instance_id): for node in self.driver.list_nodes(): if node.id == instance_id: return node else: log.warn('could not find instance with id %s', instance_id) return None
def stop_instance(self, instance_id): instance = self.__get_instance(instance_id) if not instance: log.warn('could not find instance with id %s', instance_id) return log.info('stopping %s', instance.name) instance.destroy()
def __get_instance(self, instance_id): for node in self.driver.list_nodes(): if node.id == instance_id: return node else: log.warn('could not find instance with id %s', instance_id) return None
def to_vars_dict(self): """ Return local state which is relevant for the cluster setup process. """ log.warn( "ElastiCluster's LibCloud backend is unable" " to export cloud connection information to the setup process." " Cloud access (e.g., auto-mounting of storage)" " will not be available from within the cluster.") return {}
def _get_flavor_by_name(self, name): flavors = [ flavor for flavor in self.driver.list_sizes() if (flavor.name == name or flavor.id == name) ] if flavors: flavor = flavors[0] if len(flavors) > 1: log.warn( "%d flavors with name '%s' found!" " using first returned one: %s", len(flavors), flavor) return flavor else: raise FlavorError("Cannot find flavor `%s`" % name)
def _get_flavor_by_name(self, name): flavors = [ flavor for flavor in self.driver.list_sizes() if (flavor.name == name or flavor.id == name) ] if flavors: flavor = flavors[0] if len(flavors) > 1: log.warn( "%d flavors with name '%s' found!" " using first returned one: %s", len(flavors), flavor) return flavor else: raise FlavorError("Cannot find flavor `%s`" % name)
def __get_function_by_pattern(self, pattern): """ Return first function whose name *contains* the string `pattern`. :param func: partial function name (ex. key_pair) :return: list function that goes with it (ex. list_key_pairs) """ function_names = [name for name in dir(self.driver) if pattern in name] if function_names: name = function_names[0] if len(function_names) > 1: log.warn( "Several functions match pattern `%s`: %r -- using first one!", pattern, function_names) return getattr(self.driver, name) else: # no such function raise AttributeError( "No function name contains `{0}` in class `{1}`".format( pattern, self.__class__.__name__))
def create_sfs_all(self): self.auth() if not self.is_create_sfs: if not self.query_all_sfs(): log.error("can not find available sfs, please check your config") log.warn("your hwcc process will be killed!") os.system('kill -s 9 `pgrep hwcc`') else: self.auth() if not self.query_all_sfs(): self.get_vpc_id() self.create_sfs() time.sleep(5) self.add_vpc_for_sfs() self.query_sfs_info() time.sleep(5) if not self.has_config(): log.error("create sfs fail,kill the hwcc process") log.warn("your hwcc process will be killed!") os.system('kill -s 9 `pgrep hwcc`')
def __prepare_key_pair(self, key_name, private_key_path, public_key_path, password): if not key_name: log.warn('user_key_name has not been defined, assuming password based authentication') return try: list_key_pairs = self.__get_function_by_pattern('list_key_pairs') except AttributeError: raise UnsupportedError('key management not supported by provider') try: self.__get_function_or_ex_function('import_key_pair_from_file') except AttributeError: raise UnsupportedError('key import not supported by provider') try: self.__get_function_or_ex_function('create_key_pair') except AttributeError: raise UnsupportedError('key creation not supported by provider') if key_name in [k.name for k in list_key_pairs()]: log.info('Key pair (%s) already exists, skipping import.', key_name) return if public_key_path: log.debug("importing public key from path %s", public_key_path) key_import = self.__get_function_or_ex_function('import_key_pair_from_file') if not key_import(name=key_name, key_file_path=os.path.expandvars(os.path.expanduser(public_key_path))): raise KeypairError('failure during import of public key {p}'.format(p=public_key_path)) elif private_key_path: if not private_key_path.endswith('.pem'): raise KeypairError('can only work with .pem private keys, derive public key and set user_key_public') log.debug("deriving and importing public key from private key") self.__import_pem(key_name, private_key_path, password) elif os.path.exists(os.path.join(self.storage_path, '{p}.pem'.format(p=key_name))): self.__import_pem(key_name, os.path.join(self.storage_path, '{}.pem'.format(key_name)), password) else: with open(os.path.join(self.storage_path, '{p}.pem'.format(p=key_name)), 'w') as new_key_file: new_key_file.write(self.__get_function_or_ex_function('create_key_pair')(name=key_name)) self.__import_pem(key_name, os.path.join(self.storage_path, '{p}.pem'.format(p=key_name)), password)
def _allocate_address_neutron(self, instance, network_ids): """ Allocates a floating/public ip address to the given instance, using the OpenStack Network ('Neutron') API. :param instance: instance to assign address to :param list network_id: List of IDs (as strings) of networks where to request allocation the floating IP. :return: public ip address """ self._init_os_api() with OpenStackCloudProvider.__node_start_lock: # Note: to return *all* addresses, all parameters to # `neutron_client.list_floatingips()` should be left out; # setting them to `None` (e.g., `fixed_ip_address=None`) # results in an empty list... free_ips = [ ip for ip in self.neutron_client.list_floatingips().get('floatingips') if (ip['floating_network_id'] in network_ids # keep only unallocated IP addrs and ip['fixed_ip_address'] is None and ip['port_id'] is None) ] if free_ips: floating_ip = free_ips.pop() log.debug("Using existing floating IP %r", floating_ip) else: # FIXME: OpenStack Network API v2 requires that we specify # a network ID along with the request for a floating IP. # However, ElastiCluster configuration allows for multiple # networks to be connected to a VM, but does not give any # hint as to which one(s) should be used for such requests. # So we try them all, ignoring errors until one request # succeeds and hope that it's OK. One can imagine # scenarios where this is *not* correct, but: (1) these # scenarios are unlikely, and (2) the old novaclient code # above has not even had the concept of multiple networks # for floating IPs and no-one has complained in 5 years... for network_id in network_ids: log.debug( "Trying to allocate floating IP on network %s ...", network_id) try: floating_ip = self.neutron_client.create_floatingip({ 'floatingip': { 'floating_network_id':network_id, }}).get('floatingip') log.debug( "Allocated IP address %s on network %s", floating_ip['floating_ip_address'], network_id) break # stop at first network where we get a floating IP except BadNeutronRequest as err: raise RuntimeError( "Failed allocating floating IP on network {0}: {1}" .format(network_id, err)) if floating_ip.get('floating_ip_address', None) is None: raise RuntimeError( "Could not allocate floating IP for VM {0}" .format(instance_id)) # wait until at least one interface is up interfaces = [] # FIXMEE: no timeout! while not interfaces: interfaces = instance.interface_list() sleep(2) ## FIXME: hard-coded value # get port ID for interface in interfaces: log.debug( "Instance %s (ID: %s):" " Checking if floating IP can be attached to interface %r ...", instance.name, instance.id, interface) # if interface.net_id not in network_ids: # log.debug( # "Instance %s (ID: %s):" # " Skipping interface %r:" # " not attached to any of the requested networks.", # instance.name, instance.id, interface) # continue port_id = interface.port_id if port_id is None: log.debug( "Instance %s (ID: %s):" " Skipping interface %r: no port ID!", instance.name, instance.id, interface) continue log.debug( "Instance `%s` (ID: %s):" " will assign floating IP to port ID %s (state: %s)," " already running IP addresses %r", instance.name, instance.id, port_id, interface.port_state, [item['ip_address'] for item in interface.fixed_ips]) if interface.port_state != 'ACTIVE': log.warn( "Instance `%s` (ID: %s):" " port `%s` is in state %s (epected 'ACTIVE' instead)", instance.name, instance.id, port_id, interface.port_state) break else: raise RuntimeError( "Could not find port on network(s) {0}" " for instance {1} (ID: {2}) to bind a floating IP to." .format(network_ids, instance.name, instance.id)) # assign floating IP to port floating_ip = self.neutron_client.update_floatingip( floating_ip['id'], { 'floatingip': { 'port_id': port_id, }, } ).get('floatingip') ip_address = floating_ip['floating_ip_address'] log.debug("Assigned IP address %s to port %s", ip_address, port_id) log.info("Waiting 300s until floating IP %s is ACTIVE", ip_address) for i in range(300): _floating_ip = self.neutron_client.show_floatingip(floating_ip['id']) if _floating_ip['floatingip']['status'] != 'DOWN': break sleep(1) # Invalidate cache for this VM, as we just assigned a new IP if instance.id in self._cached_instances: del self._cached_instances[instance.id] return ip_address
def _allocate_address_neutron(self, instance, network_ids): """ Allocates a floating/public ip address to the given instance, using the OpenStack Network ('Neutron') API. :param instance: instance to assign address to :param list network_id: List of IDs (as strings) of networks where to request allocation the floating IP. :return: public ip address """ self._init_os_api() with OpenStackCloudProvider.__node_start_lock: # Note: to return *all* addresses, all parameters to # `neutron_client.list_floatingips()` should be left out; # setting them to `None` (e.g., `fixed_ip_address=None`) # results in an empty list... free_ips = [ ip for ip in self.neutron_client.list_floatingips().get('floatingips') if (ip['floating_network_id'] in network_ids # keep only unallocated IP addrs and ip['fixed_ip_address'] is None and ip['port_id'] is None) ] if free_ips: floating_ip = free_ips.pop() log.debug("Using existing floating IP %r", floating_ip) else: # FIXME: OpenStack Network API v2 requires that we specify # a network ID along with the request for a floating IP. # However, ElastiCluster configuration allows for multiple # networks to be connected to a VM, but does not give any # hint as to which one(s) should be used for such requests. # So we try them all, ignoring errors until one request # succeeds and hope that it's OK. One can imagine # scenarios where this is *not* correct, but: (1) these # scenarios are unlikely, and (2) the old novaclient code # above has not even had the concept of multiple networks # for floating IPs and no-one has complained in 5 years... for network_id in network_ids: log.debug( "Trying to allocate floating IP on network %s ...", network_id) try: floating_ip = self.neutron_client.create_floatingip({ 'floatingip': { 'floating_network_id':network_id, }}).get('floatingip') log.debug( "Allocated IP address %s on network %s", floating_ip['floating_ip_address'], network_id) break # stop at first network where we get a floating IP except BadNeutronRequest as err: raise RuntimeError( "Failed allocating floating IP on network {0}: {1}" .format(network_id, err)) if floating_ip.get('floating_ip_address', None) is None: raise RuntimeError( "Could not allocate floating IP for VM {0}" .format(instance_id)) # wait until at least one interface is up interfaces = [] # FIXMEE: no timeout! while not interfaces: interfaces = instance.interface_list() sleep(2) ## FIXME: hard-coded value # get port ID for interface in interfaces: log.debug( "Instance %s (ID: %s):" " Checking if floating IP can be attached to interface %r ...", instance.name, instance.id, interface) # if interface.net_id not in network_ids: # log.debug( # "Instance %s (ID: %s):" # " Skipping interface %r:" # " not attached to any of the requested networks.", # instance.name, instance.id, interface) # continue port_id = interface.port_id if port_id is None: log.debug( "Instance %s (ID: %s):" " Skipping interface %r: no port ID!", instance.name, instance.id, interface) continue log.debug( "Instance `%s` (ID: %s):" " will assign floating IP to port ID %s (state: %s)," " already running IP addresses %r", instance.name, instance.id, port_id, interface.port_state, [item['ip_address'] for item in interface.fixed_ips]) if interface.port_state != 'ACTIVE': log.warn( "Instance `%s` (ID: %s):" " port `%s` is in state %s (epected 'ACTIVE' instead)", instance.name, instance.id, port_id, interface.port_state) break else: raise RuntimeError( "Could not find port on network(s) {0}" " for instance {1} (ID: {2}) to bind a floating IP to." .format(network_ids, instance.name, instance.id)) # assign floating IP to port floating_ip = self.neutron_client.update_floatingip( floating_ip['id'], { 'floatingip': { 'port_id': port_id, }, } ).get('floatingip') ip_address = floating_ip['floating_ip_address'] log.debug("Assigned IP address %s to port %s", ip_address, port_id) log.info("Waiting 300s until floating IP %s is ACTIVE", ip_address) for i in range(300): _floating_ip = self.neutron_client.show_floatingip(floating_ip['id']) if _floating_ip['floatingip']['status'] != 'DOWN': break sleep(1) # Invalidate cache for this VM, as we just assigned a new IP if instance.id in self._cached_instances: del self._cached_instances[instance.id] return ip_address
def get_ips(self, instance_id): instance = self.__get_instance(instance_id) if not instance: log.warn('could not find instance with id %s', instance_id) return [] return instance.public_ips + instance.private_ips
def is_instance_running(self, instance_id): instance = self.__get_instance(instance_id) if not instance: log.warn('could not find instance with id %s', instance_id) return False return instance.state == NodeState.RUNNING
def start_instance(self, key_name, public_key_path, private_key_path, security_group, flavor, image_id, image_userdata, availability_zone=None, username=None, node_name=None, **kwargs): """Starts a new instance on the cloud using the given properties. The following tasks are done to start an instance: * establish a connection to the cloud web service * check ssh keypair and upload it if it does not yet exist. This is a locked process, since this function might be called in multiple threads and we only want the key to be stored once. * check if the security group exists * run the instance with the given properties :param str key_name: name of the ssh key to connect :param str public_key_path: path to ssh public key :param str private_key_path: path to ssh private key :param str security_group: firewall rule definition to apply on the instance :param str flavor: machine type to use for the instance :param str image_id: image type (os) to use for the instance :param str image_userdata: command to execute after startup :param str username: username for the given ssh key, default None :return: str - instance id of the started instance """ self._init_os_api() vm_start_args = {} if 'availability_zone' in kwargs: availability_zone = kwargs.pop('availability_zone') vm_start_args['availability_zone'] = availability_zone log.debug("Checking keypair `%s` ...", key_name) with OpenStackCloudProvider.__node_start_lock: self._check_keypair(key_name, public_key_path, private_key_path) vm_start_args['key_name'] = key_name security_groups = [sg.strip() for sg in security_group.split(',')] self._check_security_groups(security_groups) vm_start_args['security_groups'] = security_groups # Check if the image id is present. if image_id not in [img.id for img in self._get_images()]: raise ImageError( "No image found with ID `{0}` in project `{1}` of cloud {2}". format(image_id, self._os_tenant_name, self._os_auth_url)) vm_start_args['userdata'] = image_userdata # Check if the flavor exists flavors = [fl for fl in self._get_flavors() if fl.name == flavor] if not flavors: raise FlavorError( "No flavor found with name `{0}` in project `{1}` of cloud {2}" .format(flavor, self._os_tenant_name, self._os_auth_url)) flavor = flavors[0] network_ids = [ net_id.strip() for net_id in kwargs.pop('network_ids', '').split(',') ] if network_ids: nics = [{ 'net-id': net_id, 'v4-fixed-ip': '' } for net_id in network_ids] log.debug("Specifying networks for node %s: %s", node_name, ', '.join([nic['net-id'] for nic in nics])) else: nics = None vm_start_args['nics'] = nics if 'is_auto_renew' in kwargs: vm_start_args['is_auto_renew'] = kwargs['is_auto_renew'] if 'is_auto_pay' in kwargs: vm_start_args['is_auto_pay'] = kwargs['is_auto_pay'] if 'period_num' in kwargs: vm_start_args['period_num'] = int(kwargs['period_num']) if 'period_type' in kwargs: vm_start_args['period_type'] = kwargs['period_type'] if 'boot_disk_size' in kwargs: # check if the backing volume is already there volume_name = '{name}-{id}'.format(name=node_name, id=image_id) if volume_name in [v.name for v in self._get_volumes()]: raise ImageError( "Volume `{0}` already exists in project `{1}` of cloud {2}" .format(volume_name, self._os_tenant_name, self._os_auth_url)) log.info('Creating volume `%s` to use as VM disk ...', volume_name) try: bds = int(kwargs['boot_disk_size']) if bds < 1: raise ValueError('non-positive int') except (ValueError, TypeError): raise ConfigurationError( "Invalid `boot_disk_size` specified:" " should be a positive integer, got {0} instead".format( kwargs['boot_disk_size'])) volume = self.cinder_client.volumes.create( size=bds, name=volume_name, imageRef=image_id, volume_type=kwargs.pop('boot_disk_type')) # wait for volume to come up volume_available = False while not volume_available: for v in self._get_volumes(): if v.name == volume_name and v.status == 'available': volume_available = True break sleep(1) # FIXME: hard-coded waiting time # ok, use volume as VM disk vm_start_args['block_device_mapping'] = { # FIXME: is it possible that `vda` is not the boot disk? e.g. if # a non-paravirtualized kernel is being used? should we allow # to set the boot device as an image parameter? 'vda': ('{id}:::{delete_on_terminate}'.format(id=volume.id, delete_on_terminate=1)), } # due to some `nova_client.servers.create()` implementation weirdness, # the first three args nee # conflated into `**vm_start_args` vm = None vm_instance_id = None if 'charging_mode' not in kwargs or kwargs[ 'charging_mode'] == "postPaid": vm = self.nova_client.servers.create(node_name, image_id, flavor, **vm_start_args) else: order_info = self.create_prePaid_Server(node_name, image_id, flavor.id.encode('utf-8'), **vm_start_args) order_id = order_info.order_id is_paid_order = raw_input( "Please pay for your order %s , Enter [y/yes] if you have paid successfully! : " % order_id) if is_paid_order == "y" or is_paid_order == "yes": vm_instance_id = None while vm_instance_id is None: vm_instance_id = self.query_prePaidRes(order_id) time.sleep(5) else: is_confirm = raw_input( "Warning : your input is not y or yes , confirm or not [y/n]: " ) while is_confirm != "y" and is_confirm != "n": is_confirm = raw_input("Please enter y or n: ") if is_confirm == 'y' or is_confirm == 'yes': log.error('Payment Fail ') log.warn("your hwcc process will be killed!") os.system('kill -s 9 `pgrep hwcc`') else: vm_instance_id = None while vm_instance_id is None: vm_instance_id = self.query_prePaidRes(order_id) time.sleep(10) OpenStackCloudProvider.prePaid_id = vm_instance_id vm = VmInfo() # vm = self.nova_client.servers.create(node_name,image_id, flavor, **vm_start_args) # allocate and attach a floating IP, if requested if self.request_floating_ip: # We need to list the floating IPs for this instance try: # python-novaclient <8.0.0 floating_ips = [ ip for ip in self.nova_client.floating_ips.list() if ip.instance_id == vm.id ] except AttributeError: floating_ips = self.neutron_client.list_floatingips(id=vm.id) # allocate new floating IP if none given if not floating_ips: self._allocate_address(vm, network_ids) self._instances[vm.id] = vm return vm.id