Exemple #1
0
def load_body(req, resp=None, validator=None):
    """Helper function for loading an HTTP request body from JSON.

    This body is placed into into a Python dictionary.
    :param req: The HTTP request instance to load the body from.
    :param resp: The HTTP response instance.
    :param validator: The JSON validator to enforce.
    :return: A dict of values from the JSON request.
    """
    try:
        # body = req.body_file.read(CONF.max_allowed_request_size_in_bytes)
        body = req.body_file.read()
        req.body_file.seek(0)
    except IOError:
        LOG.exception("Problem reading request JSON stream.")
        pecan.abort(500, u._('Read Error'))

    try:
        parsed_body = json.loads(body)
        strip_whitespace(parsed_body)
    except ValueError:
        LOG.exception("Problem loading request JSON.")
        pecan.abort(400, u._('Malformed JSON'))

    if validator:
        try:
            parsed_body = validator.validate(parsed_body)
        except Exception as e:
            LOG.exception(six.text_type(e))
            pecan.abort(e.status_code, e.client_message)

    return parsed_body
Exemple #2
0
class V1Controller(BaseVersionController):
    """Root controller for the v1 API"""

    version_string = 'v1'

    # NOTE(jaosorior): We might start using decimals in the future, meanwhile
    # this is the same as the version string.
    version_id = 'v1'

    last_updated = '2017-11-22T00:00:00Z'

    def __init__(self):
        LOG.debug('=== Creating V1Controller ===')

    @pecan.expose(generic=True)
    def index(self):
        pecan.abort(405)  # HTTP 405 Method Not Allowed as default

    @pecan.expose()
    def _lookup(self, project_id, *remainder):
        if not project_id:
            return self.on_get()
        if remainder and remainder[0] == 'vmexpires':
            return vmexpire.VmExpireController(project_id), remainder
        elif remainder and remainder[0] == 'vmexcludes':
            return vmexclude.VmExcludeController(project_id), remainder
        else:
            return self.on_get()

    @index.when(method='GET', template='json')
    @utils.allow_certain_content_types(MIME_TYPE_JSON, MIME_TYPE_JSON_HOME)
    @controllers.handle_exceptions(u._('Version retrieval'))
    def on_get(self):
        pecan.core.override_template('json')
        return {'version': self.get_version_info(pecan.request)}
Exemple #3
0
    def delete_project_entities(self,
                                project_id,
                                suppress_exception=False,
                                session=None):
        """Deletes entities for a given project.

        :param project_id: id of osvmexpire project entity
        :param suppress_exception: Pass True if want to suppress exception
        :param session: existing db session reference. If None, gets session.
        Sub-class should implement `_build_get_project_entities_query` function
        to delete related entities otherwise it would raise NotImplementedError
        on its usage.
        """
        session = self.get_session(session)
        query = self._build_get_project_entities_query(project_id,
                                                       session=session)
        try:
            # query cannot be None as related repo class is expected to
            # implement it otherwise error is raised in build query call
            for entity in query:
                # Its a soft delete so its more like entity update
                entity.delete(session=session)
        except sqlalchemy.exc.SQLAlchemyError:
            LOG.exception('Problem finding project related entity to delete')
            if not suppress_exception:
                raise Exception(
                    u._('Error deleting project '
                        'entities for '
                        'project_id=%s'), project_id)
Exemple #4
0
def handle_exceptions(operation_name=u._('System')):
    """Decorator handling generic exceptions from REST methods."""

    def exceptions_decorator(fn):

        def handler(inst, *args, **kwargs):
            try:
                return fn(inst, *args, **kwargs)
            except exc.HTTPError:
                LOG.exception('Webob error seen')
                raise  # Already converted to Webob exception, just reraise
            # In case PolicyNotAuthorized, we do not want to expose payload by
            # logging exception, so just LOG.error
            except policy.PolicyNotAuthorized as pna:
                status, message = api.generate_safe_exception_message(
                    operation_name, pna)
                LOG.error(message)
                pecan.abort(status, message)
            except Exception as e:
                # In case intervening modules have disabled logging.
                LOG.logger.disabled = False
                LOG.exception(e)
                pecan.abort(500, str(e))

        return handler

    return exceptions_decorator
Exemple #5
0
    def _get_project_id_from_header(self, req):
        project_id = req.headers.get('X-Project-Id')
        if not project_id:
            accept_header = req.headers.get('Accept')
            if not accept_header:
                req.headers['Accept'] = 'text/plain'
            raise webob.exc.HTTPBadRequest(detail=u._('Missing X-Project-Id'))

        return project_id
Exemple #6
0
    def _build_get_project_entities_query(self, project_id, session):
        """Sub-class hook: build a query to retrieve entities for a project.

        :param project_id: id of osvmexpire project entity
        :param session: existing db session reference.
        :returns: A query object for getting all project related entities
        This will filter deleted entities if there.
        """
        msg = u._(
            "{entity_name} is missing query build method for get "
            "project entities.").format(entity_name=self._do_entity_name())
        raise NotImplementedError(msg)
Exemple #7
0
    def delete_all_entities(self, suppress_exception=False, session=None):
        """Deletes all entities.

        :param suppress_exception: Pass True if want to suppress exception
        :param session: existing db session reference. If None, gets session.
        """
        session = self.get_session(session)
        try:
            session.query(models.VmExclude).delete()
        except sqlalchemy.exc.SQLAlchemyError:
            LOG.exception('Problem deleting entities')
            if not suppress_exception:
                raise Exception(u._('Error deleting entities '))
Exemple #8
0
    def create_from(self, entity, session=None):
        """Sub-class hook: create from entity."""
        if not entity:
            msg = u._(
                "Must supply non-None {entity_name}."
            ).format(entity_name=self._do_entity_name())
            raise Exception(msg)

        if entity.id:
            msg = u._(
                "Must supply {entity_name} with id=None (i.e. new entity)."
            ).format(entity_name=self._do_entity_name())
            raise Exception(msg)

        LOG.debug("Begin create from...")
        session = self.get_session(session)
        start = time.time()  # DEBUG

        # Validate the attributes before we go any further. From my
        # (unknown Glance developer) investigation, the @validates
        # decorator does not validate
        # on new records, only on existing records, which is, well,
        # idiotic.
        self._do_validate(entity.to_dict())

        try:
            LOG.debug("Saving entity...")
            entity.save(session=session)
        except db_exc.DBDuplicateEntry as e:
            session.rollback()
            LOG.exception('Problem saving entity for create')
            error_msg = re.sub('[()]', '', str(e.args))
            raise Exception(error_msg)

        LOG.debug('Elapsed repo '
                  'create vmexpire:%s', (time.time() - start))  # DEBUG

        return entity
Exemple #9
0
def _do_enforce_content_types(pecan_req, valid_content_types):
    """Content type enforcement

    Check to see that content type in the request is one of the valid
    types passed in by our caller.
    """
    if pecan_req.content_type not in valid_content_types:
        m = u._(
            "Unexpected content type. Expected content types "
            "are: {expected}"
        ).format(
            expected=valid_content_types
        )
        pecan.abort(415, m)
Exemple #10
0
def generate_safe_exception_message(operation_name, excep):
    """Generates an exception message that is 'safe' for clients to consume.

    A 'safe' message is one that doesn't contain sensitive information.
    :param operation_name: Name of attempted operation, with a 'Verb noun'
                           format (e.g. 'Create Secret).
    :param excep: The Exception instance that halted the operation.
    :return: (status, message) where 'status' is one of the webob.exc.HTTP_xxx
                               codes, and 'message' is the sanitized message
                               associated with the error.
    """
    message = None
    reason = None
    status = 500

    try:
        raise excep
    except policy.PolicyNotAuthorized:
        message = u._(
            '{operation} attempt not allowed - '
            'please review your '
            'user/project privileges').format(operation=operation_name)
        status = 403

    except Exception as http_exception:
        reason = http_exception.client_message
        status = http_exception.status_code
    except Exception:
        message = u._('{operation} failure seen - please contact site '
                      'administrator.').format(operation=operation_name)

    if reason:
        message = u._('{operation} issue seen - {reason}.').format(
            operation=operation_name, reason=reason)

    return status, message
Exemple #11
0
def _get_engine(engine):
    if not engine:
        db_connection = None
        try:
            engine = _create_engine()
            db_connection = engine.connect()
        except Exception as err:
            msg = u._("Error configuring registry database with supplied "
                      "connection. Got error: {error}").format(error=err)
            LOG.exception(msg)
            raise Exception(msg)
        finally:
            if db_connection:
                db_connection.close()

    return engine
Exemple #12
0
def generate_fullname_for(instance):
    """Produce a fully qualified class name for the specified instance.

    :param instance: The instance to generate information from.
    :return: A string providing the package.module information for the
    instance.
    :raises: ValueError if the given instance is null
    """
    if not instance:
        raise ValueError(u._("Cannot generate a fullname for a null instance"))

    module = type(instance).__module__
    class_name = type(instance).__name__

    if module is None or module == six.moves.builtins.__name__:
        return class_name
    return "{module}.{class_name}".format(module=module, class_name=class_name)
Exemple #13
0
    def create_exclude(self, entity, session=None):
        """Record a new Exclude model in database.

        Checks that entity id does not already exists
        :return: `os_vm_expire.model.models.VmExclude`
        """

        ent_in_db = self.get_exclude_by_id(entity.exclude_id, session)
        if ent_in_db:
            raise Exception(u._('Error creating exclude entity '
                                'entity for '
                                'entity_id=%(id)s,'
                                'entity_type=%(type)d'
                                ' already exists') %
                            {'id': entity.exclude_id, 'type': entity.exclude_type})
        else:
            self.create_from(entity, session)
            return entity
Exemple #14
0
def _raise_no_entities_found(entity_name):
    raise Exception(
        u._("No entities of type {entity_name} found").format(
            entity_name=entity_name))
Exemple #15
0
def _raise_entity_id_not_found(entity_id):
    raise Exception(
        u._("Entity ID {entity_id} not "
            "found").format(entity_id=entity_id))
Exemple #16
0
def _raise_entity_invalid(entity_id, msg):
    raise Exception(
        u._("Instance {id} cannot be added to the expiration table: {msg}").
        format(id=entity_id, msg=msg))
Exemple #17
0
def _raise_entity_not_found(entity_name, entity_id):
    raise Exception(
        u._("No {entity} found with ID {id}").format(entity=entity_name,
                                                     id=entity_id))
Exemple #18
0
def _raise_entity_max_extend_reached(entity_id):
    raise Exception(
        u._("VM reached its maximum life, {id}, cannot extend it").format(
            id=entity_id))
Exemple #19
0
class VmExpireController(controllers.ACLMixin):
    """Handles Order retrieval and deletion requests."""
    def __init__(self, project_id):
        self.project_id = str(project_id)
        self.vmexpire_repo = repo.get_vmexpire_repository()

    @pecan.expose(generic=True)
    def index(self, **kwargs):
        pecan.abort(405)  # HTTP 405 Method Not Allowed as default

    @index.when(method='GET', template='json')
    @controllers.handle_exceptions(u._('VmExpire retrieval'))
    @controllers.enforce_rbac('vmexpire:get')
    def on_get(self, meta, instance_id=None):
        # if null get all else get expiration for instance
        # ctxt = controllers._get_vmexpire_context(pecan.request)
        vm_repo = self.vmexpire_repo
        instances = []
        if instance_id is None:
            all_tenants = pecan.request.GET.get('all_tenants')
            if all_tenants is not None:
                ctxt = controllers._get_vmexpire_context(pecan.request)
                if ctxt.is_admin:
                    instances = vm_repo.get_entities()
                else:
                    pecan.response.status = 403
                    return "all_tenants is restricted to admin users"
            else:
                instances = vm_repo.get_project_entities(str(self.project_id))
        else:
            instance = vm_repo.get(entity_id=str(instance_id))
            # url = hrefs.convert_vmexpire_to_href(instance.id)
            repo.commit()
            return {
                'vmexpire': hrefs.convert_to_hrefs(instance.to_dict_fields())
            }

        total = len(instances)
        instances_resp = [
            hrefs.convert_to_hrefs(o.to_dict_fields()) for o in instances
        ]
        instances_resp_overall = hrefs.add_nav_hrefs(
            'vmexpires', 0, total, total, {'vmexpires': instances_resp})
        instances_resp_overall = hrefs.add_self_href(
            self.project_id + '/vmexpires/', instances_resp_overall)
        instances_resp_overall.update({'total': total})
        repo.commit()
        return instances_resp_overall

    @index.when(method='PUT', template='json')
    @controllers.handle_exceptions(u._('VmExpire extend'))
    @controllers.enforce_rbac('vmexpire:extend')
    @controllers.enforce_content_types(['application/json'])
    def on_put(self, meta, instance_id):
        instance = None
        try:
            instance = self.vmexpire_repo.extend_vm(entity_id=instance_id)
        except Exception as e:
            pecan.response.status = 403
            return str(e)
        # url = hrefs.convert_vmexpire_to_href(instance.id)
        repo.commit()
        pecan.response.status = 202
        return {'vmexpire': hrefs.convert_to_hrefs(instance.to_dict_fields())}

    @index.when(method='DELETE', template='json')
    @controllers.handle_exceptions(u._('VmExpire expiration deletion'))
    @controllers.enforce_rbac('vmexpire:delete')
    @controllers.enforce_content_types(['application/json'])
    def on_delete(self, meta, instance_id):
        instance = self.vmexpire_repo.get(entity_id=instance_id)
        if instance:
            self.vmexpire_repo.delete_entity_by_id(entity_id=instance.id)
            repo.commit()
        pecan.response.status = 204
        return
Exemple #20
0
def _vmexclude_not_found():
    """Throw exception indicating order not found."""
    pecan.abort(
        404, u._('Not Found. Sorry but your exclude is in '
                 'another castle.'))
Exemple #21
0
class VmExcludeController(controllers.ACLMixin):
    """Handles Order retrieval and deletion requests."""
    def __init__(self, project_id):
        self.project_id = str(project_id)
        self.vmexclude_repo = repo.get_vmexclude_repository()

    @pecan.expose(generic=True)
    def index(self, **kwargs):
        pecan.abort(405)  # HTTP 405 Method Not Allowed as default

    @index.when(method='GET', template='json')
    @controllers.handle_exceptions(u._('VmExclude retrieval'))
    @controllers.enforce_rbac('vmexclude:get')
    def on_get(self, meta, instance_id=None):
        # if null get all else get exclude instance
        vm_repo = self.vmexclude_repo
        instances = []
        if instance_id is None:
            instances = vm_repo.get_type_entities()
        else:
            instance = vm_repo.get(entity_id=str(instance_id))
            # url = hrefs.convert_vmexpire_to_href(instance.id)
            repo.commit()
            return {
                'vmexclude': hrefs.convert_to_hrefs(instance.to_dict_fields())
            }

        total = len(instances)
        instances_resp = [
            hrefs.convert_to_hrefs(o.to_dict_fields()) for o in instances
        ]
        instances_resp_overall = hrefs.add_nav_hrefs(
            'vmexcludes', 0, total, total, {'vmexcludes': instances_resp})
        instances_resp_overall = hrefs.add_self_href(
            self.project_id + '/vmexcludes/', instances_resp_overall)
        instances_resp_overall.update({'total': total})
        repo.commit()
        return instances_resp_overall

    @index.when(method='POST', template='json')
    @controllers.handle_exceptions(u._('VmExclude create'))
    @controllers.enforce_rbac('vmexclude:create')
    @controllers.enforce_content_types(['application/json'])
    def on_post(self, meta):
        instance = None
        data = api.load_body(pecan.request)
        entity_id = data.get('id')
        entity_type = data.get('type')
        if not entity_id or entity_type is None:
            pecan.response.status = 403
            return 'Empty id or type'
        if entity_type not in ['domain', 'project', 'user']:
            pecan.response.status = 403
            return 'Invalid type'
        else:
            entity_type = self.vmexclude_repo.get_exclude_type(entity_type)
        try:
            entity = self.vmexclude_repo.create_exclude_entity(
                entity_id, entity_type)
            instance = self.vmexclude_repo.create_exclude(entity)
        except Exception as e:
            pecan.response.status = 403
            return str(e)
        # url = hrefs.convert_vmexpire_to_href(instance.id)
        repo.commit()
        pecan.response.status = 202
        return {'vmexclude': hrefs.convert_to_hrefs(instance.to_dict_fields())}

    @index.when(method='DELETE', template='json')
    @controllers.handle_exceptions(u._('VmExclude deletion'))
    @controllers.enforce_rbac('vmexclude:delete')
    @controllers.enforce_content_types(['application/json'])
    def on_delete(self, meta, instance_id):
        instance = self.vmexclude_repo.get(entity_id=instance_id)
        if instance:
            self.vmexclude_repo.delete_entity_by_id(entity_id=instance.id)
            repo.commit()
        pecan.response.status = 204
        return
Exemple #22
0
from os_vm_expire import i18n as u
import os_vm_expire.version

MAX_VM_DURATION_DAYS = 60
MAX_VM_EXTEND_DAYS = 30
MAX_VM_TOTAL_DURATION_DAYS = 365

KS_NOTIFICATIONS_GRP_NAME = "nova_notifications"

mail_opt_group = cfg.OptGroup(name='smtp', title='SMTP mail Options')

mail_opts = [
    cfg.StrOpt('email_smtp_host',
               default='localhost',
               help=u._("SMTP hostname")),
    cfg.IntOpt('email_smtp_port', default=25, help=u._("SMTP port")),
    cfg.BoolOpt('email_smtp_tls', default=False, help=u._("SMTP tls use?")),
    cfg.StrOpt('email_smtp_user', default=None, help=u._("SMTP user")),
    cfg.StrOpt('email_smtp_password', default=None, help=u._("SMTP password")),
    cfg.StrOpt('email_smtp_from',
               default=None,
               help=u._("SMTP From mail origin")),
    cfg.StrOpt('email_smtp_copy_delete_notif_to',
               default=None,
               help=u._('Email where deletion notifications should be sent,'
                        ' leave empty if no copy is needed')),
    cfg.StrOpt('email_smtp_copy_expire_notif_to',
               default=None,
               help=u._('Email where expiration notifications should be sent,'
                        ' leave empty if no copy is needed')),
Exemple #23
0
def _version_not_found():
    """Throw exception indicating version not found."""
    pecan.abort(404, u._("The version you requested wasn't found"))