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
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)}
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)
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
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
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)
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 '))
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
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)
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
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
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)
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
def _raise_no_entities_found(entity_name): raise Exception( u._("No entities of type {entity_name} found").format( entity_name=entity_name))
def _raise_entity_id_not_found(entity_id): raise Exception( u._("Entity ID {entity_id} not " "found").format(entity_id=entity_id))
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))
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))
def _raise_entity_max_extend_reached(entity_id): raise Exception( u._("VM reached its maximum life, {id}, cannot extend it").format( id=entity_id))
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
def _vmexclude_not_found(): """Throw exception indicating order not found.""" pecan.abort( 404, u._('Not Found. Sorry but your exclude is in ' 'another castle.'))
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
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')),
def _version_not_found(): """Throw exception indicating version not found.""" pecan.abort(404, u._("The version you requested wasn't found"))