def update(self, req, id, body): """Update a snapshot.""" LOG.info(_LI("Update snapshot with id: %s"), id) context = req.environ['sgservice.context'] if not body: msg = _("Missing request body") raise webob.exc.HTTPBadRequest(explanation=msg) if 'snapshot' not in body: msg = (_("Missing required element '%s' in request body"), 'snapshot') raise webob.exc.HTTPBadRequest(explanation=msg) snapshot = body['snapshot'] update_dict = {} valid_update_keys = ( 'name', 'description', 'display_name', 'display_description', ) for key in valid_update_keys: if key in snapshot: update_dict[key] = snapshot[key] self.validate_name_and_description(update_dict) if 'name' in update_dict: update_dict['display_name'] = update_dict.pop('name') if 'description' in update_dict: update_dict['display_description'] = update_dict.pop('description') snapshot = self.service_api.get_snapshot(context, id) snapshot.update(update_dict) snapshot.save() return self._view_builder.detail(req, snapshot)
def create(self, req, body): """Creates a new replication.""" if not self.is_valid_body(body, 'replication'): raise exc.HTTPUnprocessableEntity() LOG.debug('Create replication request body: %s', body) context = req.environ['sgservice.context'] replication = body['replication'] master_volume_id = replication.get('master_volume', None) if master_volume_id is None: msg = _('Incorrect request body format') raise webob.exc.HTTPBadRequest(explanation=msg) slave_volume_id = replication.get('slave_volume', None) if slave_volume_id is None: msg = _('Incorrect request body format') raise webob.exc.HTTPBadRequest(explanation=msg) name = replication.get('name', None) description = replication.get('description', None) if description is None: description = 'replication:%s-%s' % (master_volume_id, slave_volume_id) master_volume = self.service_api.get(context, master_volume_id) slave_volume = self.service_api.get(context, slave_volume_id) replication = self.service_api.create_replication( context, name, description, master_volume, slave_volume) return self._view_builder.detail(req, replication)
def update(self, req, id, body): """Update a backup.""" LOG.info(_LI("Update backup, backup_id: %s"), id) context = req.environ['sgservice.context'] if not body: msg = _("Missing request body") raise webob.exc.HTTPBadRequest(explanation=msg) if 'backup' not in body: msg = (_("Missing required element '%s' in request body"), 'backup') raise webob.exc.HTTPBadRequest(explanation=msg) backup = body['backup'] update_dict = {} valid_update_keys = ( 'name', 'description', 'display_name', 'display_description', ) for key in valid_update_keys: if key in backup: update_dict[key] = backup[key] self.validate_name_and_description(update_dict) if 'name' in update_dict: update_dict['display_name'] = update_dict.pop('name') if 'description' in update_dict: update_dict['display_description'] = update_dict.pop('description') backup = self.service_api.get_backup(context, id) backup.update(update_dict) backup.save() return self._view_builder.detail(req, backup)
def create(self, req, body): """Creates a new backup.""" LOG.debug('Create backup request body: %s', body) context = req.environ['sgservice.context'] backup = body['backup'] volume_id = backup.get('volume_id', None) if volume_id is None: msg = _('Incorrect request body format') raise webob.exc.HTTPBadRequest(explanation=msg) name = backup.get('name', None) description = backup.get('description', None) if description is None: description = 'backup-%s' % volume_id backup_type = backup.get('type') if backup_type is None: backup_type = constants.FULL_BACKUP if backup_type not in constants.SUPPORT_BACKUP_TYPES: msg = _('backup type should be full or incremental') raise webob.exc.HTTPBadRequest(explanation=msg) backup_destination = backup.get('destination') if backup_destination is None: backup_destination = constants.LOCAL_BACKUP if backup_destination not in constants.SUPPORT_BACKUP_DESTINATIONS: msg = _('backup destination should be local or remote') raise webob.exc.HTTPBadRequest(explanation=msg) volume = self.service_api.get(context, volume_id) backup = self.service_api.create_backup(context, name, description, volume, backup_type, backup_destination) return self._view_builder.detail(req, backup)
def validate_integer(value, name, min_value=None, max_value=None): """Make sure that value is a valid integer, potentially within range. :param value: the value of the integer :param name: the name of the integer :param min_length: the min_length of the integer :param max_length: the max_length of the integer :returns: integer """ try: value = int(value) except (TypeError, ValueError, UnicodeEncodeError): raise webob.exc.HTTPBadRequest( explanation=(_('%s must be an integer.') % name)) if min_value is not None and value < min_value: raise webob.exc.HTTPBadRequest( explanation=(_('%(value_name)s must be >= %(min_value)d') % { 'value_name': name, 'min_value': min_value })) if max_value is not None and value > max_value: raise webob.exc.HTTPBadRequest( explanation=(_('%(value_name)s must be <= %(max_value)d') % { 'value_name': name, 'max_value': max_value })) return value
def _get_offset_param(params): """Extract offset id from request's dictionary (defaults to 0) or fail.""" try: offset = int(params.pop('offset', 0)) except ValueError: msg = _('offset param must be an integer') raise webob.exc.HTTPBadRequest(explanation=msg) if offset < 0: msg = _('offset param must be positive') raise webob.exc.HTTPBadRequest(explanation=msg) return offset
def reset_status(self, req, id, body): """reset replication status""" LOG.info(_LI("Reset replication status, id: %s"), id) status = body['reset_status'].get('status', fields.ReplicationStatus.ENABLED) if status not in fields.ReplicationStatus.ALL: msg = _("Invalid status provided.") LOG.error(msg) raise exception.InvalidStatus(status=status) context = req.environ['sgservice.context'] replication = self.service_api.get_replication(context, id) replication.status = status replication.save() # reset master volume replicate status master_volume = self.service_api.get(context, replication.master_volume) if master_volume.replicate_status not in [ None, fields.ReplicateStatus.DELETED ]: master_volume.replicate_status = status master_volume.save() # reset slave volume replicate status slave_volume = self.service_api.get(context, replication.slave_volume) if slave_volume.replicate_status not in [ None, fields.ReplicateStatus.DELETED ]: slave_volume.replicate_status = status slave_volume.save() return webob.Response(status_int=202)
def reset_status(self, req, id, body): """reset checkpoint status""" LOG.info(_LI("Reset checkpoint status, id: %s"), id) status = body['reset_status'].get('status', fields.CheckpointStatus.AVAILABLE) if status not in fields.CheckpointStatus.ALL: msg = _("Invalid status provided.") LOG.error(msg) raise exception.InvalidStatus(status=status) context = req.environ['sgservice.context'] checkpoint = self.service_api.get_checkpoint(context, id) checkpoint.status = status checkpoint.save() # reset master snapshot status try: master_snapshot = self.service_api.get_snapshot( context, checkpoint.master_snapshot) master_snapshot.status = status master_snapshot.save() except Exception: pass # reset slave snapshot status try: slave_snapshot = self.service_api.get_snapshot( context, checkpoint.slave_snapshot) slave_snapshot.status = status slave_snapshot.save() except Exception: pass return webob.Response(status_int=202)
def __init__(self, name, loader=None): """Initialize, but do not start the WSGI server. :param name: The name of the WSGI server given to the loader. :param loader: Loads the WSGI application using the given name. :returns: None """ self.name = name self.manager = self._get_manager() self.loader = loader or wsgi_common.Loader() self.app = self.loader.load_app(name) self.host = getattr(CONF, '%s_listen' % name, "0.0.0.0") self.port = getattr(CONF, '%s_listen_port' % name, 0) self.workers = (getattr(CONF, '%s_workers' % name, None) or processutils.get_worker_count()) if self.workers and self.workers < 1: worker_name = '%s_workers' % name msg = (_("%(worker_name)s value of %(workers)d is invalid, " "must be greater than 0.") % {'worker_name': worker_name, 'workers': self.workers}) raise exception.InvalidInput(msg) self.server = wsgi.Server(name, self.app, host=self.host, port=self.port)
def action_peek_json(body): """Determine action to invoke.""" try: decoded = jsonutils.loads(body) except ValueError: msg = _("cannot understand JSON") raise exception.MalformedRequestBody(reason=msg) # Make sure there's exactly one key... if len(decoded) != 1: msg = _("too many body keys") raise exception.MalformedRequestBody(reason=msg) # Return the action and the decoded body... return list(decoded.keys())[0]
def _error(self, inner, req): LOG.exception(_LE("Caught error: %(type)s %(error)s"), { 'type': type(inner), 'error': inner }) safe = getattr(inner, 'safe', False) headers = getattr(inner, 'headers', None) status = getattr(inner, 'code', 500) if status is None: status = 500 msg_dict = dict(url=req.url, status=status) LOG.info(_LI("%(url)s returned with HTTP %(status)d"), msg_dict) outer = self.status_to_type(status) if headers: outer.headers = headers # NOTE(johannes): We leave the explanation empty here on # purpose. It could possibly have sensitive information # that should not be returned back to the user. See # bugs 868360 and 874472 # NOTE(eglynn): However, it would be over-conservative and # inconsistent with the EC2 API to hide every exception, # including those that are safe to expose, see bug 1021373 if safe: msg = (inner.msg if isinstance(inner, exception.SGServiceException) else six.text_type(inner)) params = { 'exception': inner.__class__.__name__, 'explanation': msg } outer.explanation = _('%(exception)s: %(explanation)s') % params return wsgi.Fault(outer)
def create(self): if self.obj_attr_is_set('id'): raise exception.ObjectActionError(action='create', reason=_('already created')) updates = self.sgservice_obj_get_changes() db_service = db.service_create(self._context, updates) self._from_db_object(self._context, self, db_service)
def create(self): if self.obj_attr_is_set('id'): raise exception.ObjectActionError(action='create', reason=_('already created')) updates = self.sgservice_obj_get_changes() with self.obj_as_admin(): db_attachment = db.volume_attach(self._context, updates) self._from_db_object(self._context, self, db_attachment)
def _get_limit_param(params, max_limit=None): """Extract integer limit from request's dictionary or fail. Defaults to max_limit if not present and returns max_limit if present 'limit' is greater than max_limit. """ max_limit = max_limit or CONF.osapi_max_limit try: limit = int(params.pop('limit', max_limit)) except ValueError: msg = _('limit param must be an integer') raise webob.exc.HTTPBadRequest(explanation=msg) if limit <= 0: msg = _('limit param must be positive') raise webob.exc.HTTPBadRequest(explanation=msg) limit = min(limit, max_limit) return limit
def list(self): """Show a list of all sgservice services.""" ctxt = context.get_admin_context() services = db.service_get_all(ctxt) print_format = "%-16s %-36s %-10s %-5s %-10s" print( print_format % (_('Binary'), _('Host'), _('Status'), _('State'), _('Updated At'))) for svc in services: alive = utils.service_is_up(svc) art = ":-)" if alive else "XXX" status = 'enabled' if svc['disabled']: status = 'disabled' print(print_format % (svc['binary'], svc['host'].partition('.')[0], status, art, svc['updated_at']))
def get_bool_params(param_string, params): param = params.get(param_string, False) if not is_valid_boolstr(param): msg = _('Value %(param)s for %(param_string)s is not a boolean') % { 'param': param, 'param_string': param_string } raise exception.InvalidParameterValue(err=msg) return strutils.bool_from_string(param, strict=True)
def assert_valid_body(body, entity_name): # NOTE: After v1 api is deprecated need to merge 'is_valid_body' and # 'assert_valid_body' in to one method. Right now it is not # possible to modify 'is_valid_body' to raise exception because # in case of V1 api when 'is_valid_body' return False, # 'HTTPUnprocessableEntity' exception is getting raised and in # V2 api 'HTTPBadRequest' exception is getting raised. if not Controller.is_valid_body(body, entity_name): raise webob.exc.HTTPBadRequest( explanation=_("Missing required element '%s' in " "request body.") % entity_name)
def __call__(self, req): headers = req.headers environ = req.environ user_id = headers.get('X_USER_ID') or headers.get('X_USER') if user_id is None: LOG.debug("Neither X_USER_ID nor X_USER found in request") return webob.exc.HTTPUnauthorized() # get the roles roles = [r.strip() for r in headers.get('X_ROLE', '').split(',')] if 'X_TENANT_ID' in headers: # This is the new header since Keystone went to ID/Name project_id = headers['X_TENANT_ID'] else: # This is for legacy compatibility project_id = headers['X_TENANT'] project_name = headers.get('X_TENANT_NAME') req_id = environ.get(request_id.ENV_REQUEST_ID) # Get the auth token auth_token = headers.get('X_AUTH_TOKEN', headers.get('X_STORAGE_TOKEN')) # Build a context, including the auth_token... remote_address = req.remote_addr auth_token_info = environ.get('keystone.token_info') service_catalog = None if headers.get('X_SERVICE_CATALOG') is not None: try: catalog_header = headers.get('X_SERVICE_CATALOG') service_catalog = jsonutils.loads(catalog_header) except ValueError: raise webob.exc.HTTPInternalServerError( explanation=_('Invalid service catalog json.')) if CONF.use_forwarded_for: remote_address = headers.get('X-Forwarded-For', remote_address) ctx = context.RequestContext(user_id, project_id, project_name=project_name, roles=roles, auth_token=auth_token, remote_address=remote_address, service_catalog=service_catalog, request_id=req_id, auth_token_info=auth_token_info) environ['sgservice.context'] = ctx return self.application
def restore(self, req, id, body): """Restore backup to an SG-enabled volume""" LOG.info(_LI("Restore backup to sg-enabled volume, backup_id: %s"), id) context = req.environ['sgservice.context'] backup = self.service_api.get_backup(context, id) restore = body['restore'] volume_id = restore.get('volume_id', None) if volume_id is None: msg = _('restored volume should be specified.') raise webob.exc.HTTPBadRequest(explanation=msg) restore = self.service_api.restore_backup(context, backup, volume_id) return self._view_builder.restore_summary(req, restore)
def get_by_id(cls, context, id, *args, **kwargs): # To get by id we need to have a model and for the model to # have an id field if 'id' not in cls.fields: msg = (_('VersionedObject %s cannot retrieve object by id.') % (cls.obj_name())) raise NotImplementedError(msg) model = getattr(models, cls.obj_name()) orm_obj = db.get_by_id(context, model, id, *args, **kwargs) kargs = {} if hasattr(cls, 'DEFAULT_EXPECTED_ATTR'): kargs = {'expected_attrs': getattr(cls, 'DEFAULT_EXPECTED_ATTR')} return cls._from_db_object(context, cls(context), orm_obj, **kargs)
def obj_load_attr(self, attrname): if attrname not in self.OPTIONAL_FIELDS: raise exception.ObjectActionError( action='obj_load_attr', reason=_('attribute %s not lazy-loadable') % attrname) if not self._context: raise exception.OrphanedObjectError(method='obj_load_attr', objtype=self.obj_name()) if attrname == 'volume': volume = objects.Volume.get_by_id(self._context, self.volume_id) self.volume = volume self.obj_reset_changes(fields=[attrname])
def reset_status(self, req, id, body): """reset volume status""" LOG.info(_LI("Reset volume status, id: %s"), id) status = body['reset_status'].get('status', fields.VolumeStatus.ENABLED) if status not in fields.VolumeStatus.ALL: msg = _("Invalid status provided.") LOG.error(msg) raise exception.InvalidStatus(status=status) context = req.environ['sgservice.context'] volume = self.service_api.get(context, id) volume.status = status volume.save() return webob.Response(status_int=202)
def reset_status(self, req, id, body): """reset snapshot status""" LOG.info(_LI("Reset snapshot status, id: %s"), id) status = body['reset_status'].get('status', fields.SnapshotStatus.AVAILABLE) if status not in fields.SnapshotStatus.ALL: msg = _("Invalid status provided.") LOG.error(msg) raise exception.InvalidStatus(status=status) context = req.environ['sgservice.context'] snapshot = self.service_api.get_snapshot(context, id) snapshot.status = status snapshot.save() return webob.Response(status_int=202)
def attach(self, req, id, body): """Add sg-volume attachment metadata.""" LOG.info(_LI("Add SG-volume attachment, volume_id: %s"), id) context = req.environ['sgservice.context'] volume = self.service_api.get(context, id) params = body['attach'] params = {} if params is None else params instance_uuid = params.get('instance_uuid', None) mode = params.get('mode', None) if mode is None: mode = 'rw' if instance_uuid is None: msg = _("Invalid request to attach volume to an invalid target") raise webob.exc.HTTPBadRequest(explanation=msg) if mode not in ('rw', 'ro'): msg = _("Invalid request to attach volume to an invalid mode. " "Attaching mode should be 'rw' or 'ro'.") raise webob.exc.HTTPBadRequest(explanation=msg) attach_result = self.service_api.attach(context, volume, instance_uuid, mode) return self._view_builder.attach_summary(req, attach_result)
def create(self, req, body): """Creates a new volume from snapshot or checkpoint.""" LOG.debug('Create volume from snapshot, request body: %s', body) context = req.environ['sgservice.context'] volume = body['volume'] volume_type = volume.get('volume_type', None) availability_zone = volume.get('availability_zone', None) volume_id = volume.get('volume_id', None) size = volume.get('size', None) # create from snapshot snapshot_id = volume.get('snapshot_id') if snapshot_id is not None: name = volume.get('name', 'volume-%s' % snapshot_id) description = volume.get('description', name) snapshot = self.service_api.get_snapshot(context, snapshot_id) volume = self.service_api.create_volume( context, snapshot=snapshot, volume_type=volume_type, availability_zone=availability_zone, description=description, name=name, volume_id=volume_id, size=size) return self._view_builder.detail(req, volume) # create from checkpoint checkpoint_id = volume.get('checkpoint_id') if checkpoint_id is not None: name = volume.get('name', 'volume-%s' % checkpoint_id) description = volume.get('description', name) checkpoint = self.service_api.get_checkpoint( context, checkpoint_id) volume = self.service_api.create_volume( context, checkpoint=checkpoint, volume_type=volume_type, availability_zone=availability_zone, description=description, name=name, volume_id=volume_id) return self._view_builder.detail(req, volume) msg = _('Incorrect request body format, create volume must specified ' 'a snapshot or checkpoint') raise webob.exc.HTTPBadRequest(explanation=msg)
def obj_load_attr(self, attrname): if attrname not in self.OPTIONAL_FIELDS: raise exception.ObjectActionError( action='obj_load_attr', reason=_('attribute %s not lazy-loadable') % attrname) if not self._context: raise exception.OrphanedObjectError(method='obj_load_attr', objtype=self.obj_name()) if attrname == 'metadata': self.metadata = db.volume_metadata_get(self._context, self.id) elif attrname == 'volume_attachment': attachments = objects.VolumeAttachmentList.get_all_by_volume_id( self._context, self.id) self.volume_attachment = attachments self.obj_reset_changes(fields=[attrname])
def refresh(self): # To refresh we need to have a model and for the model to have an id # field if 'id' not in self.fields: msg = (_('VersionedObject %s cannot retrieve object by id.') % (self.obj_name())) raise NotImplementedError(msg) current = self.get_by_id(self._context, self.id) for field in self.fields: # Only update attributes that are already set. We do not want to # unexpectedly trigger a lazy-load. if self.obj_attr_is_set(field): if self[field] != current[field]: self[field] = current[field] self.obj_reset_changes()
def db_sync(version=None, init_version=INIT_VERSION, engine=None): """Migrate the database to `version` or the most recent version.""" if engine is None: engine = db_api.get_engine() current_db_version = get_backend().db_version(engine, MIGRATE_REPO_PATH, init_version) # TODO(e0ne): drop version validation when new oslo.db will be released if version and int(version) < current_db_version: msg = _('Database schema downgrade is not allowed.') raise exception.InvalidInput(reason=msg) return get_backend().db_sync(engine=engine, abs_path=MIGRATE_REPO_PATH, version=version, init_version=init_version)
def get_sort_params(params, default_key='created_at', default_dir='desc'): """Retrieves sort keys/directions parameters. Processes the parameters to create a list of sort keys and sort directions that correspond to either the 'sort' parameter or the 'sort_key' and 'sort_dir' parameter values. The value of the 'sort' parameter is a comma- separated list of sort keys, each key is optionally appended with ':<sort_direction>'. Note that the 'sort_key' and 'sort_dir' parameters are deprecated in kilo and an exception is raised if they are supplied with the 'sort' parameter. The sort parameters are removed from the request parameters by this function. :param params: webob.multidict of request parameters (from sgservice.api.openstack.wsgi.Request.params) :param default_key: default sort key value, added to the list if no sort keys are supplied :param default_dir: default sort dir value, added to the list if the corresponding key does not have a direction specified :returns: list of sort keys, list of sort dirs :raise webob.exc.HTTPBadRequest: If both 'sort' and either 'sort_key' or 'sort_dir' are supplied parameters """ if 'sort' in params and ('sort_key' in params or 'sort_dir' in params): msg = _("The 'sort_key' and 'sort_dir' parameters are deprecated and " "cannot be used with the 'sort' parameter.") raise webob.exc.HTTPBadRequest(explanation=msg) sort_keys = [] sort_dirs = [] if 'sort' in params: for sort in params.pop('sort').strip().split(','): sort_key, _sep, sort_dir = sort.partition(':') if not sort_dir: sort_dir = default_dir sort_keys.append(sort_key.strip()) sort_dirs.append(sort_dir.strip()) else: sort_key = params.pop('sort_key', default_key) sort_dir = params.pop('sort_dir', default_dir) sort_keys.append(sort_key.strip()) sort_dirs.append(sort_dir.strip()) return sort_keys, sort_dirs
def limited_by_marker(items, request, max_limit=None): """Return a slice of items according to the requested marker and limit.""" max_limit = max_limit or CONF.osapi_max_limit marker, limit, __ = get_pagination_params(request.GET.copy(), max_limit) start_index = 0 if marker: start_index = -1 for i, item in enumerate(items): if 'flavorid' in item: if item['flavorid'] == marker: start_index = i + 1 break elif item['id'] == marker or item.get('uuid') == marker: start_index = i + 1 break if start_index < 0: msg = _('marker [%s] not found') % marker raise webob.exc.HTTPBadRequest(explanation=msg) range_end = start_index + limit return items[start_index:range_end]