Esempio n. 1
0
    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
Esempio n. 2
0
    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
Esempio n. 3
0
    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
Esempio n. 4
0
    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)
Esempio n. 5
0
    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)
Esempio n. 6
0
    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'])
Esempio n. 7
0
    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"))
Esempio n. 8
0
    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"))
Esempio n. 9
0
    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']
Esempio n. 10
0
 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)
Esempio n. 11
0
    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))
Esempio n. 12
0
 def test_error_msg(self):
     self.assertEqual(six.text_type(exceptions.BlazarException('test')),
                      'test')
Esempio n. 13
0
    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'])
Esempio n. 14
0
    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
Esempio n. 15
0
    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
Esempio n. 16
0
    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
Esempio n. 17
0
    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)
Esempio n. 18
0
 def test_error_msg(self):
     self.assertEqual('test',
                      str(exceptions.BlazarException('test')))