def _get_plugins(self): """Return dict of resource-plugin class pairs.""" config_plugins = CONF.manager.plugins plugins = {} extension_manager = enabled.EnabledExtensionManager( check_func=lambda ext: ext.name in config_plugins, namespace='blazar.resource.plugins', invoke_on_load=False ) invalid_plugins = (set(config_plugins) - set([ext.name for ext in extension_manager.extensions])) if invalid_plugins: raise common_ex.BlazarException('Invalid plugin names are ' 'specified: %s' % invalid_plugins) for ext in extension_manager.extensions: try: plugin_obj = ext.plugin() except Exception as e: LOG.warning("Could not load {0} plugin " "for resource type {1} '{2}'".format( ext.name, ext.plugin.resource_type, e)) else: if plugin_obj.resource_type in plugins: msg = ("You have provided several plugins for " "one resource type in configuration file. " "Please set one plugin per resource type.") raise exceptions.PluginConfigurationError(error=msg) plugins[plugin_obj.resource_type] = plugin_obj return plugins
def _allocation_candidates(self, lease, reservations): """Returns dict by resource type of reservation candidates.""" allocations = {} for reservation in reservations: res = reservation.copy() resource_type = reservation['resource_type'] res['start_date'] = lease['start_date'] res['end_date'] = lease['end_date'] if resource_type not in self.plugins: raise exceptions.UnsupportedResourceType( resource_type=resource_type) plugin = self.plugins.get(resource_type) if not plugin: raise common_ex.BlazarException( 'Invalid plugin names are specified: %s' % resource_type) candidate_ids = plugin.allocation_candidates(res) allocations[resource_type] = [ plugin.get(cid) for cid in candidate_ids] return allocations
def _existing_allocations(self, reservations): allocations = {} for reservation in reservations: resource_type = reservation['resource_type'] if resource_type not in self.plugins: raise exceptions.UnsupportedResourceType( resource_type=resource_type) plugin = self.plugins.get(resource_type) if not plugin: raise common_ex.BlazarException( 'Invalid plugin names are specified: %s' % resource_type) resource_ids = [ x['resource_id'] for x in plugin.list_allocations( dict(reservation_id=reservation['id'])) if x['reservations']] allocations[resource_type] = [ plugin.get(rid) for rid in resource_ids] return allocations
def put(self, id, sublease): """Update an existing lease. :param id: UUID of a lease. :param lease: a subset of a Lease containing values to update. """ sublease_dct = sublease.as_dict() new_name = sublease_dct.pop('name', None) end_date = sublease_dct.pop('end_date', None) start_date = sublease_dct.pop('start_date', None) before_end_date = sublease_dct.pop('before_end_date', None) if sublease_dct != {}: raise exceptions.BlazarException('Only name changing, ' 'dates and before end ' 'notifications may be ' 'proceeded.') if new_name: sublease_dct['name'] = new_name if end_date: sublease_dct['end_date'] = end_date if start_date: sublease_dct['start_date'] = start_date if before_end_date: sublease_dct['before_end_date'] = before_end_date lease = pecan.request.rpcapi.update_lease(id, sublease_dct) if lease is None: raise exceptions.NotFound(object={'lease_id': id}) return Lease.convert(lease)
def test_error_in_update_flags(self): callback = self.patch(DummyMonitorPlugin, 'poll') callback.return_value = {'dummy_id1': {'missing_resources': True}} update_flags = self.patch(self.monitor, '_update_flags') update_flags.side_effect = exceptions.BlazarException('error') # Testing that no exception is raised even if the callback raises one self.monitor.call_monitor_plugin(callback)
def delete_lease(self, lease_id): lease = self.get_lease(lease_id) if (datetime.datetime.utcnow() >= lease['start_date'] and datetime.datetime.utcnow() <= lease['end_date']): start_event = db_api.event_get_first_sorted_by_filters( 'lease_id', 'asc', { 'lease_id': lease_id, 'event_type': 'start_lease' } ) if not start_event: raise common_ex.BlazarException( 'start_lease event for lease %s not found' % lease_id) end_event = db_api.event_get_first_sorted_by_filters( 'lease_id', 'asc', { 'lease_id': lease_id, 'event_type': 'end_lease', 'status': status.event.UNDONE } ) if not end_event: raise common_ex.BlazarException( 'end_lease event for lease %s not found' % lease_id) db_api.event_update(end_event['id'], {'status': status.event.IN_PROGRESS}) with trusts.create_ctx_from_trust(lease['trust_id']) as ctx: for reservation in lease['reservations']: if reservation['status'] != status.reservation.DELETED: plugin = self.plugins[reservation['resource_type']] try: plugin.on_end(reservation['resource_id']) except (db_ex.BlazarDBException, RuntimeError): LOG.exception("Failed to delete a reservation " "for a lease.") raise db_api.lease_destroy(lease_id) self._send_notification(lease, ctx, events=['delete'])
def post(self, lease): """Creates a new lease. :param lease: a lease within the request body. """ # FIXME(sbauza): DB exceptions are currently catched and return a lease # equal to None instead of being sent to the API lease_dct = lease.as_dict() lease = pecan.request.rpcapi.create_lease(lease_dct) if lease is not None: return Lease.convert(lease) else: raise exceptions.BlazarException(_("Lease can't be created"))
def post(self, host): """Creates a new host. :param host: a host within the request body. """ # here API should go to Keystone API v3 and create trust host_dct = host.as_dict() # FIXME(sbauza): DB exceptions are currently catched and return a lease # equal to None instead of being sent to the API host = pecan.request.hosts_rpcapi.create_computehost(host_dct) if host is not None: return Host.convert(host) else: raise exceptions.BlazarException(_("Host can't be created"))
def reserve_resource(self, reservation_id, values): self.validate_reservation_param(values) # TODO(masahito) the instance reservation plugin only supports # anti-affinity rule in short-term goal. if bool_from_string(values['affinity']): raise exceptions.BlazarException('affinity = True is not ' 'supported.') hosts = self.pickup_hosts(reservation_id, values) if len(hosts['added']) < values['amount']: raise mgr_exceptions.HostNotFound("The reservation can't be " "accommodate because of less " "capacity.") instance_reservation_val = { 'reservation_id': reservation_id, 'vcpus': values['vcpus'], 'memory_mb': values['memory_mb'], 'disk_gb': values['disk_gb'], 'amount': values['amount'], 'affinity': bool_from_string(values['affinity']), } instance_reservation = db_api.instance_reservation_create( instance_reservation_val) for host_id in hosts['added']: db_api.host_allocation_create({ 'compute_host_id': host_id, 'reservation_id': reservation_id }) try: flavor, group, pool = self._create_resources(instance_reservation) except nova_exceptions.ClientException: LOG.exception( "Failed to create Nova resources " "for reservation %s", reservation_id) self.cleanup_resources(instance_reservation) raise mgr_exceptions.NovaClientError() db_api.instance_reservation_update( instance_reservation['id'], { 'flavor_id': flavor.id, 'server_group_id': group.id, 'aggregate_id': pool.id }) return instance_reservation['id']
def replacement_start_response(status, headers, exc_info=None): """Overrides the default response to make errors parsable.""" try: status_code = int(status.split(' ')[0]) except (ValueError, TypeError): # pragma: nocover raise exceptions.BlazarException( _('Status {0} was unexpected').format(status)) else: if status_code >= 400: # Remove some headers so we can replace them later # when we have the full error message and can # compute the length. headers = [(h, v) for (h, v) in headers if h.lower() != 'content-length'] # Save the headers as we need to modify them. state['status_code'] = status_code state['headers'] = headers state['exc_info'] = exc_info return start_response(status, headers, exc_info)
def __init__(self): extensions = [] self.extension_manager = enabled.EnabledExtensionManager( check_func=lambda ext: ext.name in CONF.api.api_v2_controllers, namespace='blazar.api.v2.controllers.extensions', invoke_on_load=True) self._log_missing_plugins(CONF.api.api_v2_controllers) for ext in self.extension_manager.extensions: try: setattr(self, ext.obj.name, ext.obj) except TypeError: raise exceptions.BlazarException( _("API name must be specified for " "extension {0}").format(ext.name)) self._routes.update(ext.obj.extra_routes) extensions.append(ext.obj.name) LOG.debug("Loaded extensions: {0}".format(extensions))
def test_error_msg(self): self.assertEqual(six.text_type(exceptions.BlazarException('test')), 'test')
def delete_lease(self, lease_id): lease = self.get_lease(lease_id) start_event = db_api.event_get_first_sorted_by_filters( 'lease_id', 'asc', { 'lease_id': lease_id, 'event_type': 'start_lease', } ) if not start_event: raise common_ex.BlazarException( 'start_lease event for lease %s not found' % lease_id) end_event = db_api.event_get_first_sorted_by_filters( 'lease_id', 'asc', { 'lease_id': lease_id, 'event_type': 'end_lease', } ) if not end_event: raise common_ex.BlazarException( 'end_lease event for lease %s not found' % lease_id) lease_already_started = start_event['status'] != status.event.UNDONE lease_not_started = not lease_already_started lease_already_ended = end_event['status'] != status.event.UNDONE lease_not_ended = not lease_already_ended end_lease = lease_already_started and lease_not_ended if end_lease: db_api.event_update(end_event['id'], {'status': status.event.IN_PROGRESS}) with trusts.create_ctx_from_trust(lease['trust_id']) as ctx: reservations = lease['reservations'] if lease_not_started or lease_not_ended: # Only run the on_end enforcement if we're explicitly # ending the lease for the first time OR if we're terminating # it before the lease ever started. It's important to run # on_end in the second case to inform enforcement that the # lease is no longer in play. allocations = self._existing_allocations(reservations) try: self.enforcement.on_end(ctx, lease, allocations) except Exception as e: LOG.error(e) for reservation in reservations: if reservation['status'] != status.reservation.DELETED: plugin = self.plugins[reservation['resource_type']] try: plugin.on_end(reservation['resource_id']) except (db_ex.BlazarDBException, RuntimeError): LOG.exception("Failed to delete a reservation " "for a lease.") raise db_api.lease_destroy(lease_id) self._send_notification(lease, ctx, events=['delete'])
def update_lease(self, lease_id, values): if not values: return db_api.lease_get(lease_id) if len(values) == 1 and 'name' in values: db_api.lease_update(lease_id, values) return db_api.lease_get(lease_id) lease = db_api.lease_get(lease_id) start_date = values.get( 'start_date', datetime.datetime.strftime(lease['start_date'], LEASE_DATE_FORMAT)) end_date = values.get( 'end_date', datetime.datetime.strftime(lease['end_date'], LEASE_DATE_FORMAT)) before_end_date = values.get('before_end_date', None) now = datetime.datetime.utcnow() now = datetime.datetime(now.year, now.month, now.day, now.hour, now.minute) if start_date == 'now': start_date = now else: start_date = self._date_from_string(start_date) if end_date == 'now': end_date = now else: end_date = self._date_from_string(end_date) values['start_date'] = start_date values['end_date'] = end_date if (lease['start_date'] < now and values['start_date'] != lease['start_date']): raise common_ex.NotAuthorized( 'Cannot modify the start date of already started leases') if (lease['start_date'] > now and values['start_date'] < now): raise common_ex.NotAuthorized( 'Start date must be later than current date') if lease['end_date'] < now: raise common_ex.NotAuthorized( 'Terminated leases can only be renamed') if (values['end_date'] < now or values['end_date'] < values['start_date']): raise common_ex.NotAuthorized( 'End date must be later than current and start date') with trusts.create_ctx_from_trust(lease['trust_id']): if before_end_date: try: before_end_date = self._date_from_string(before_end_date) self._check_date_within_lease_limits(before_end_date, values) except common_ex.BlazarException as e: LOG.error("Invalid before_end_date param. %s", str(e)) raise e # TODO(frossigneux) rollback if an exception is raised reservations = values.get('reservations', []) for reservation in ( db_api.reservation_get_all_by_lease_id(lease_id)): v = {} v['start_date'] = values['start_date'] v['end_date'] = values['end_date'] try: v.update([r for r in reservations if r['id'] == reservation['id']].pop()) except KeyError: raise exceptions.MissingParameter(param='reservation ID') except IndexError: pass resource_type = v.get('resource_type', reservation['resource_type']) if resource_type != reservation['resource_type']: raise exceptions.CantUpdateParameter( param='resource_type') self.plugins[resource_type].update_reservation( reservation['id'], v) event = db_api.event_get_first_sorted_by_filters( 'lease_id', 'asc', { 'lease_id': lease_id, 'event_type': 'start_lease' } ) if not event: raise common_ex.BlazarException( 'Start lease event not found') db_api.event_update(event['id'], {'time': values['start_date']}) event = db_api.event_get_first_sorted_by_filters( 'lease_id', 'asc', { 'lease_id': lease_id, 'event_type': 'end_lease' } ) if not event: raise common_ex.BlazarException( 'End lease event not found') db_api.event_update(event['id'], {'time': values['end_date']}) notifications = ['update'] self._update_before_end_event(lease, values, notifications, before_end_date) try: del values['reservations'] except KeyError: pass db_api.lease_update(lease_id, values) lease = db_api.lease_get(lease_id) with trusts.create_ctx_from_trust(lease['trust_id']) as ctx: self._send_notification(lease, ctx, events=notifications) return lease
def update_reservation(self, reservation_id, new_values): """Updates an instance reservation with requested parameters. This method allows users to update an instance reservation under the following conditions. - If an instance reservation has not started yet - vcpus, memory_mb disk_gb and amount can be updateable unless Blazar can accomodate the new request. - If an instance reservation has already started - only amount is increasable. """ # TODO(masahito) the instance reservation plugin only supports # anti-affinity rule in short-term goal. if bool_from_string(new_values.get('affinity', None)): raise exceptions.BlazarException('affinity = True is not ' 'supported.') reservation = db_api.reservation_get(reservation_id) lease = db_api.lease_get(reservation['lease_id']) updatable = ['vcpus', 'memory_mb', 'disk_gb', 'affinity', 'amount'] if (not any([k in updatable for k in new_values.keys()]) and new_values['start_date'] >= lease['start_date'] and new_values['end_date'] <= lease['end_date']): # no update because of just shortening the reservation time return if (reservation['status'] == 'active' and any([k in updatable[:-1] for k in new_values.keys()])): msg = "An active reservation only accepts to update amount." raise mgr_exceptions.InvalidStateUpdate(msg) if reservation['status'] == 'error': msg = "An error reservation doesn't accept an updating request." raise mgr_exceptions.InvalidStateUpdate(msg) for key in updatable: if key not in new_values: new_values[key] = reservation[key] changed_hosts = self.pickup_hosts(reservation_id, new_values) if (reservation['status'] == 'active' and len(changed_hosts['removed']) > 0): err_msg = ("Instance reservation doesn't allow to reduce/replace " "reserved instance slots when the reservation is in " "active status.") raise mgr_exceptions.CantUpdateParameter(err_msg) db_api.instance_reservation_update( reservation['resource_id'], {key: new_values[key] for key in updatable}) self.update_host_allocations(changed_hosts['added'], changed_hosts['removed'], reservation_id) try: self.update_resources(reservation_id) except mgr_exceptions.NovaClientError: raise
def update_lease(self, lease_id, values): if not values: return db_api.lease_get(lease_id) if len(values) == 1 and 'name' in values: db_api.lease_update(lease_id, values) return db_api.lease_get(lease_id) lease = db_api.lease_get(lease_id) start_date = values.get( 'start_date', datetime.datetime.strftime(lease['start_date'], LEASE_DATE_FORMAT)) end_date = values.get( 'end_date', datetime.datetime.strftime(lease['end_date'], LEASE_DATE_FORMAT)) before_end_date = values.get('before_end_date', None) start_date, end_date, now = self._parse_lease_dates(start_date, end_date) values['start_date'] = start_date values['end_date'] = end_date self._check_for_invalid_date_inputs(lease, values, now) with trusts.create_ctx_from_trust(lease['trust_id']): if before_end_date: try: before_end_date = self._date_from_string(before_end_date) self._check_date_within_lease_limits(before_end_date, values) except common_ex.BlazarException as e: LOG.error("Invalid before_end_date param. %s", str(e)) raise e reservations = values.get('reservations', []) existing_reservations = ( db_api.reservation_get_all_by_lease_id(lease_id)) try: invalid_ids = set([r['id'] for r in reservations]).difference( [r['id'] for r in existing_reservations]) except KeyError: raise exceptions.MissingParameter(param='reservation ID') if invalid_ids: raise common_ex.InvalidInput( 'Please enter valid reservation IDs. Invalid reservation ' 'IDs are: %s' % ','.join([str(id) for id in invalid_ids])) try: [ self.plugins[r['resource_type']] for r in (reservations + existing_reservations)] except KeyError: raise exceptions.CantUpdateParameter(param='resource_type') existing_allocs = self._existing_allocations(existing_reservations) if reservations: new_reservations = reservations new_allocs = self._allocation_candidates(values, existing_reservations) else: # User is not updating reservation parameters, e.g., is only # adjusting lease start/end dates. new_reservations = existing_reservations new_allocs = existing_allocs try: self.enforcement.check_update(context.current(), lease, values, existing_allocs, new_allocs, existing_reservations, new_reservations) except common_ex.NotAuthorized as e: LOG.error("Enforcement checks failed. %s", str(e)) raise common_ex.NotAuthorized(e) # TODO(frossigneux) rollback if an exception is raised for reservation in existing_reservations: v = {} v['start_date'] = values['start_date'] v['end_date'] = values['end_date'] try: v.update([r for r in reservations if r['id'] == reservation['id']].pop()) except IndexError: pass resource_type = v.get('resource_type', reservation['resource_type']) if resource_type != reservation['resource_type']: raise exceptions.CantUpdateParameter( param='resource_type') self.plugins[resource_type].update_reservation( reservation['id'], v) event = db_api.event_get_first_sorted_by_filters( 'lease_id', 'asc', { 'lease_id': lease_id, 'event_type': 'start_lease' } ) if not event: raise common_ex.BlazarException( 'Start lease event not found') db_api.event_update(event['id'], {'time': values['start_date']}) event = db_api.event_get_first_sorted_by_filters( 'lease_id', 'asc', { 'lease_id': lease_id, 'event_type': 'end_lease' } ) if not event: raise common_ex.BlazarException( 'End lease event not found') db_api.event_update(event['id'], {'time': values['end_date']}) notifications = ['update'] self._update_before_end_event(lease, values, notifications, before_end_date) try: del values['reservations'] except KeyError: pass db_api.lease_update(lease_id, values) lease = db_api.lease_get(lease_id) with trusts.create_ctx_from_trust(lease['trust_id']) as ctx: self._send_notification(lease, ctx, events=notifications) return lease
def test_error_in_callback(self): callback = self.patch(DummyMonitorPlugin, 'poll') callback.side_effect = exceptions.BlazarException('error') # Testing that no exception is raised even if the callback raises one self.monitor.call_monitor_plugin(callback)
def test_error_msg(self): self.assertEqual('test', str(exceptions.BlazarException('test')))