def _matching_hosts(self, hypervisor_properties, resource_properties, count_range, start_date, end_date): """Return the matching hosts (preferably not allocated) """ count_range = count_range.split('-') min_host = count_range[0] max_host = count_range[1] allocated_host_ids = [] not_allocated_host_ids = [] filter_array = [] start_date_with_margin = start_date - datetime.timedelta( minutes=CONF.cleaning_time) end_date_with_margin = end_date + datetime.timedelta( minutes=CONF.cleaning_time) # TODO(frossigneux) support "or" operator if hypervisor_properties: filter_array = plugins_utils.convert_requirements( hypervisor_properties) if resource_properties: filter_array += plugins_utils.convert_requirements( resource_properties) for host in db_api.reservable_host_get_all_by_queries(filter_array): if not db_api.host_allocation_get_all_by_values( compute_host_id=host['id']): not_allocated_host_ids.append(host['id']) elif db_utils.get_free_periods( host['id'], start_date_with_margin, end_date_with_margin, end_date_with_margin - start_date_with_margin) == [ (start_date_with_margin, end_date_with_margin), ]: allocated_host_ids.append(host['id']) if len(not_allocated_host_ids) >= int(min_host): return not_allocated_host_ids[:int(max_host)] all_host_ids = allocated_host_ids + not_allocated_host_ids if len(all_host_ids) >= int(min_host): return all_host_ids[:int(max_host)] else: return []
def update_host_allocations(self, added, removed, reservation_id): allocations = db_api.host_allocation_get_all_by_values( reservation_id=reservation_id) removed_allocs = [] for host_id in removed: for allocation in allocations: if allocation['compute_host_id'] == host_id: removed_allocs.append(allocation['id']) break # TODO(tetsuro): It would be nice to have something like # db_api.host_allocation_replace() to process the following # deletion and addition in *one* DB transaction. for alloc_id in removed_allocs: db_api.host_allocation_destroy(alloc_id) for added_host in added: db_api.host_allocation_create({ 'compute_host_id': added_host, 'reservation_id': reservation_id })
def update_resources(self, reservation_id): """Updates reserved resources in Nova. This method updates reserved resources in Compute service. If the reservation is in active status, it adds new allocated hosts into a reserved aggregate. If the reservation is not started yet, it updates a reserved flavor. """ reservation = db_api.reservation_get(reservation_id) if reservation['status'] == 'active': pool = nova.ReservationPool() for allocation in db_api.host_allocation_get_all_by_values( reservation_id=reservation['id']): host = db_api.host_get(allocation['compute_host_id']) try: pool.add_computehost( reservation['aggregate_id'], host['service_name'], stay_in=True) except mgr_exceptions.AggregateAlreadyHasHost: pass except nova_exceptions.ClientException: err_msg = ('Fail to add host %s to aggregate %s.' % (host, reservation['aggregate_id'])) raise mgr_exceptions.NovaClientError(err_msg) self.placement_client.update_reservation_inventory( host['service_name'], reservation['id'], 1) else: try: self.nova.nova.flavors.delete(reservation['id']) self._create_flavor(reservation['id'], reservation['vcpus'], reservation['memory_mb'], reservation['disk_gb'], reservation['server_group_id']) except nova_exceptions.ClientException: LOG.exception("Failed to update Nova resources " "for reservation %s", reservation['id']) raise mgr_exceptions.NovaClientError()
def on_start(self, resource_id): ctx = context.current() instance_reservation = db_api.instance_reservation_get(resource_id) reservation_id = instance_reservation['reservation_id'] try: self.nova.flavor_access.add_tenant_access(reservation_id, ctx.project_id) except nova_exceptions.ClientException: LOG.info('Failed to associate flavor %(reservation_id)s ' 'to project %(project_id)s', {'reservation_id': reservation_id, 'project_id': ctx.project_id}) raise mgr_exceptions.EventError() pool = nova.ReservationPool() for allocation in db_api.host_allocation_get_all_by_values( reservation_id=reservation_id): host = db_api.host_get(allocation['compute_host_id']) pool.add_computehost(instance_reservation['aggregate_id'], host['service_name'], stay_in=True) self.placement_client.update_reservation_inventory( host['service_name'], reservation_id, 1)
def on_end(self, resource_id): """Remove the hosts from the pool.""" host_reservation = db_api.host_reservation_get(resource_id) db_api.host_reservation_update(host_reservation['id'], {'status': 'completed'}) allocations = db_api.host_allocation_get_all_by_values( reservation_id=host_reservation['reservation_id']) for allocation in allocations: db_api.host_allocation_destroy(allocation['id']) pool = nova.ReservationPool() for host in pool.get_computehosts(host_reservation['aggregate_id']): for server in self.nova.servers.list( search_opts={"host": host, "all_tenants": 1}): try: self.nova.servers.delete(server=server) except nova_exceptions.NotFound: LOG.info('Could not find server %s, may have been deleted ' 'concurrently.', server) except Exception as e: LOG.exception('Failed to delete %s: %s.', server, str(e)) try: pool.delete(host_reservation['aggregate_id']) except manager_ex.AggregateNotFound: pass
def pickup_hosts(self, reservation_id, values): """Returns lists of host ids to add/remove. This function picks up available hosts, calculates the difference from old reservations and returns a dict of a list of host ids to add and remove keyed by "added" or "removed". Note that the lists allow duplicated host ids for `affinity=True` cases. :raises: NotEnoughHostsAvailable exception if there are not enough hosts available for the request """ req_amount = values['amount'] affinity = bool_from_string(values['affinity'], default=None) query_params = { 'cpus': values['vcpus'], 'memory': values['memory_mb'], 'disk': values['disk_gb'], 'resource_properties': values['resource_properties'], 'start_date': values['start_date'], 'end_date': values['end_date'] } old_allocs = db_api.host_allocation_get_all_by_values( reservation_id=reservation_id) if old_allocs: # This is a path for *update* reservation. Add the specific # query param not to consider resources reserved by existing # reservations to update query_params['excludes_res'] = [reservation_id] new_hosts = self.query_available_hosts(**query_params) old_host_id_list = [h['compute_host_id'] for h in old_allocs] candidate_id_list = [h['id'] for h in new_hosts] # Build `new_host_id_list`. Note that we'd like to pick up hosts in # the following order of priority: # 1. hosts reserved by the reservation to update # 2. hosts with reservations followed by hosts without reservations # Note that the `candidate_id_list` has already been ordered # satisfying the second requirement. if affinity: host_id_map = collections.Counter(candidate_id_list) available = {k for k, v in host_id_map.items() if v >= req_amount} if not available: raise mgr_exceptions.NotEnoughHostsAvailable() new_host_ids = set(old_host_id_list) & available if new_host_ids: # (priority 1) This is a path for update reservation. We pick # up a host from hosts reserved by the reservation to update. new_host_id = new_host_ids.pop() else: # (priority 2) This is a path both for update and for new # reservation. We pick up hosts with some other reservations # if possible and otherwise pick up hosts without any # reservation. We can do so by considering the order of the # `candidate_id_list`. for host_id in candidate_id_list: if host_id in available: new_host_id = host_id break new_host_id_list = [new_host_id] * req_amount else: # Hosts that can accommodate but don't satisfy priority 1 _, possible_host_list = plugins_utils.list_difference( old_host_id_list, candidate_id_list) # Hosts that satisfy priority 1 new_host_id_list, _ = plugins_utils.list_difference( candidate_id_list, possible_host_list) if affinity is False: # Eliminate the duplication new_host_id_list = list(set(new_host_id_list)) for host_id in possible_host_list: if (affinity is False) and (host_id in new_host_id_list): # Eliminate the duplication continue new_host_id_list.append(host_id) if len(new_host_id_list) < req_amount: raise mgr_exceptions.NotEnoughHostsAvailable() while len(new_host_id_list) > req_amount: new_host_id_list.pop() # Calculate the difference from the existing reserved host removed_host_ids, added_host_ids = plugins_utils.list_difference( old_host_id_list, new_host_id_list) return {'added': added_host_ids, 'removed': removed_host_ids}
def heal_reservations(self, failed_resources): """Heal reservations which suffer from resource failures. :param: failed_resources: a list of failed hosts. :return: a dictionary of {reservation id: flags to update} e.g. {'de27786d-bd96-46bb-8363-19c13b2c6657': {'missing_resources': True}} """ reservation_flags = {} failed_allocs = [] for host in failed_resources: failed_allocs += db_api.host_allocation_get_all_by_values( compute_host_id=host['id']) for alloc in failed_allocs: reservation = db_api.reservation_get(alloc['reservation_id']) if reservation['resource_type'] != plugin.RESOURCE_TYPE: continue lease = db_api.lease_get(reservation['lease_id']) host_reservation = None pool = None # Remove the failed host from the aggregate. if reservation['status'] == status.reservation.ACTIVE: host = db_api.host_get(alloc['compute_host_id']) host_reservation = db_api.host_reservation_get( reservation['resource_id']) with trusts.create_ctx_from_trust(lease['trust_id']): pool = nova.ReservationPool() pool.remove_computehost(host_reservation['aggregate_id'], host['service_name']) # Allocate alternative resource. start_date = max(datetime.datetime.utcnow(), lease['start_date']) new_hostids = self._matching_hosts( reservation['hypervisor_properties'], reservation['resource_properties'], '1-1', start_date, lease['end_date']) if not new_hostids: if reservation['id'] not in reservation_flags: reservation_flags[reservation['id']] = {} reservation_flags[reservation['id']].update( {'missing_resources': True}) db_api.host_allocation_destroy(alloc['id']) LOG.warn( 'Could not find alternative host for reservation %s ' '(lease: %s).', reservation['id'], lease['name']) else: new_hostid = new_hostids.pop() db_api.host_allocation_update(alloc['id'], {'compute_host_id': new_hostid}) if reservation['status'] == status.reservation.ACTIVE: # Add the alternative host into the aggregate. new_host = db_api.host_get(new_hostid) with trusts.create_ctx_from_trust(lease['trust_id']): pool.add_computehost(host_reservation['aggregate_id'], new_host['service_name']) if reservation['id'] not in reservation_flags: reservation_flags[reservation['id']] = {} reservation_flags[reservation['id']].update( {'resources_changed': True}) LOG.warn('Resource changed for reservation %s (lease: %s).', reservation['id'], lease['name']) return reservation_flags
def heal_reservations(self, failed_resources): """Heal reservations which suffer from resource failures. :param: failed_resources: a list of failed hosts. :return: a dictionary of {reservation id: flags to update} e.g. {'de27786d-bd96-46bb-8363-19c13b2c6657': {'missing_resources': True}} """ reservation_flags = {} failed_allocs = [] for host in failed_resources: failed_allocs += db_api.host_allocation_get_all_by_values( compute_host_id=host['id']) for alloc in failed_allocs: reservation = db_api.reservation_get(alloc['reservation_id']) if reservation['resource_type'] != RESOURCE_TYPE: continue pool = None # Remove the failed host from the aggregate. if reservation['status'] == status.reservation.ACTIVE: host = db_api.host_get(alloc['compute_host_id']) pool = nova.ReservationPool() pool.remove_computehost(reservation['aggregate_id'], host['service_name']) # Allocate alternative resource. values = {} lease = db_api.lease_get(reservation['lease_id']) values['start_date'] = max(datetime.datetime.utcnow(), lease['start_date']) values['end_date'] = lease['end_date'] specs = ['vcpus', 'memory_mb', 'disk_gb', 'affinity', 'amount'] for key in specs: values[key] = reservation[key] changed_hosts = self.pickup_hosts(reservation['id'], values) if len(changed_hosts['added']) == 0: if reservation['id'] not in reservation_flags: reservation_flags[reservation['id']] = {} reservation_flags[reservation['id']].update( {'missing_resources': True}) db_api.host_allocation_destroy(alloc['id']) LOG.warn( 'Could not find alternative host for reservation %s ' '(lease: %s).', reservation['id'], lease['name']) else: new_host_id = changed_hosts['added'].pop() db_api.host_allocation_update(alloc['id'], {'compute_host_id': new_host_id}) if reservation['status'] == status.reservation.ACTIVE: # Add the alternative host into the aggregate. new_host = db_api.host_get(new_host_id) pool.add_computehost(reservation['aggregate_id'], new_host['service_name'], stay_in=True) if reservation['id'] not in reservation_flags: reservation_flags[reservation['id']] = {} reservation_flags[reservation['id']].update( {'resources_changed': True}) LOG.warn('Resource changed for reservation %s (lease: %s).', reservation['id'], lease['name']) return reservation_flags