def _sanitize_query(q): '''Check the query to see if: 1) the request is comming from admin - then allow full visibility 2) non-admin - make sure that the query includes the requester's project. ''' auth_project = acl.get_limited_to_project(pecan.request.headers) if auth_project: proj_q = [i for i in q if i.field == 'project_id'] for i in proj_q: if auth_project != i.value or i.op != 'eq': # TODO(asalkeld) in the next version of wsme (0.5b3+) # activate this code to be able to return the correct # status code (also update api/v2/test_acl.py). #return wsme.api.Response([return_type()], # status_code=401) errstr = 'Not Authorized to access project %s %s' % (i.op, i.value) raise wsme.exc.ClientSideError(errstr) if not proj_q: # The user is restricted, but they didn't specify a project # so add it for them. q.append(Query(field='project_id', op='eq', value=auth_project)) return q
def compute_duration_by_resource(resource, meter): """Return the earliest timestamp, last timestamp, and duration for the resource and meter. :param resource: The ID of the resource. :param meter: The name of the meter. :param start_timestamp: ISO-formatted string of the earliest timestamp to return. :param end_timestamp: ISO-formatted string of the latest timestamp to return. :param search_offset: Number of minutes before and after start and end timestamps to query. """ q_ts = _get_query_timestamps(flask.request.args) start_timestamp = q_ts['start_timestamp'] end_timestamp = q_ts['end_timestamp'] # Query the database for the interval of timestamps # within the desired range. f = storage.SampleFilter( meter=meter, project=acl.get_limited_to_project(flask.request.headers), resource=resource, start=q_ts['query_start'], end=q_ts['query_end'], ) stats = flask.request.storage_conn.get_meter_statistics(f) min_ts, max_ts = stats.duration_start, stats.duration_end # "Clamp" the timestamps we return to the original time # range, excluding the offset. LOG.debug('start_timestamp %s, end_timestamp %s, min_ts %s, max_ts %s', start_timestamp, end_timestamp, min_ts, max_ts) if start_timestamp and min_ts and min_ts < start_timestamp: min_ts = start_timestamp LOG.debug('clamping min timestamp to range') if end_timestamp and max_ts and max_ts > end_timestamp: max_ts = end_timestamp LOG.debug('clamping max timestamp to range') # If we got valid timestamps back, compute a duration in minutes. # # If the min > max after clamping then we know the # timestamps on the samples fell outside of the time # range we care about for the query, so treat them as # "invalid." # # If the timestamps are invalid, return None as a # sentinal indicating that there is something "funny" # about the range. if min_ts and max_ts and (min_ts <= max_ts): duration = timeutils.delta_seconds(min_ts, max_ts) else: min_ts = max_ts = duration = None return flask.jsonify( start_timestamp=min_ts, end_timestamp=max_ts, duration=duration, )
def put(self, alarm_id, data): """Modify an alarm.""" conn = pecan.request.storage_conn data.state_timestamp = wsme.Unset data.alarm_id = alarm_id auth_project = acl.get_limited_to_project(pecan.request.headers) alarms = list(conn.get_alarms(alarm_id=alarm_id, project=auth_project)) if len(alarms) < 1: error = _("Unknown alarm") pecan.response.translatable_error = error raise wsme.exc.ClientSideError(error) # merge the new values from kwargs into the current # alarm "alarm_in". alarm_in = alarms[0] kwargs = data.as_dict(storage.models.Alarm) for k, v in kwargs.iteritems(): setattr(alarm_in, k, v) if k == 'state': alarm_in.state_timestamp = timeutils.utcnow() alarm = conn.update_alarm(alarm_in) return Alarm.from_db_model(alarm)
def compute_duration_by_resource(resource, meter): """Return the earliest timestamp, last timestamp, and duration for the resource and meter. :param resource: The ID of the resource. :param meter: The name of the meter. :param start_timestamp: ISO-formatted string of the earliest timestamp to return. :param end_timestamp: ISO-formatted string of the latest timestamp to return. :param search_offset: Number of minutes before and after start and end timestamps to query. """ q_ts = _get_query_timestamps(flask.request.args) start_timestamp = q_ts['start_timestamp'] end_timestamp = q_ts['end_timestamp'] # Query the database for the interval of timestamps # within the desired range. f = storage.SampleFilter( meter=meter, project=acl.get_limited_to_project(flask.request.headers), resource=resource, start=q_ts['query_start'], end=q_ts['query_end'], ) stats = flask.request.storage_conn.get_meter_statistics(f) min_ts, max_ts = stats.duration_start, stats.duration_end # "Clamp" the timestamps we return to the original time # range, excluding the offset. LOG.debug('start_timestamp %s, end_timestamp %s, min_ts %s, max_ts %s', start_timestamp, end_timestamp, min_ts, max_ts) if start_timestamp and min_ts and min_ts < start_timestamp: min_ts = start_timestamp LOG.debug('clamping min timestamp to range') if end_timestamp and max_ts and max_ts > end_timestamp: max_ts = end_timestamp LOG.debug('clamping max timestamp to range') # If we got valid timestamps back, compute a duration in minutes. # # If the min > max after clamping then we know the # timestamps on the samples fell outside of the time # range we care about for the query, so treat them as # "invalid." # # If the timestamps are invalid, return None as a # sentinal indicating that there is something "funny" # about the range. if min_ts and max_ts and (min_ts <= max_ts): duration = timeutils.delta_seconds(min_ts, max_ts) else: min_ts = max_ts = duration = None return flask.jsonify(start_timestamp=min_ts, end_timestamp=max_ts, duration=duration, )
def get_one(self, alarm_id): """Return one alarm""" conn = pecan.request.storage_conn auth_project = acl.get_limited_to_project(pecan.request.headers) alarms = list(conn.get_alarms(alarm_id=alarm_id, project=auth_project)) if len(alarms) < 1: raise wsme.exc.ClientSideError(_("Unknown alarm")) return Alarm.from_db_model(alarms[0])
def delete(self, alarm_id): """Delete an alarm""" conn = pecan.request.storage_conn auth_project = acl.get_limited_to_project(pecan.request.headers) alarms = list(conn.get_alarms(alarm_id=alarm_id, project=auth_project)) if len(alarms) < 1: raise wsme.exc.ClientSideError(_("Unknown alarm")) conn.delete_alarm(alarm_id)
def delete(self, alarm_id): """Delete an alarm.""" conn = pecan.request.storage_conn auth_project = acl.get_limited_to_project(pecan.request.headers) alarms = list(conn.get_alarms(alarm_id=alarm_id, project=auth_project)) if len(alarms) < 1: raise wsme.exc.ClientSideError(_("Unknown alarm")) conn.delete_alarm(alarm_id)
def get_one(self, alarm_id): """Return one alarm.""" conn = pecan.request.storage_conn auth_project = acl.get_limited_to_project(pecan.request.headers) alarms = list(conn.get_alarms(alarm_id=alarm_id, project=auth_project)) if len(alarms) < 1: raise wsme.exc.ClientSideError(_("Unknown alarm")) return Alarm.from_db_model(alarms[0])
def get_one(self, resource_id): """Retrieve details about one resource. :param resource_id: The UUID of the resource. """ authorized_project = acl.get_limited_to_project(pecan.request.headers) r = list(pecan.request.storage_conn.get_resources( resource=resource_id, project=authorized_project))[0] return Resource.from_db_and_links(r, self._resource_links(resource_id))
def list_meters_all(): """Return a list of meters. :param metadata.<key>: match on the metadata within the resource. (optional) """ rq = flask.request meters = rq.storage_conn.get_meters(project=acl.get_limited_to_project( rq.headers), metaquery=_get_metaquery(rq.args)) return flask.jsonify(meters=[m.as_dict() for m in meters])
def list_meters_all(): """Return a list of meters. :param metadata.<key>: match on the metadata within the resource. (optional) """ rq = flask.request meters = rq.storage_conn.get_meters( project=acl.get_limited_to_project(rq.headers), metaquery=_get_metaquery(rq.args)) return flask.jsonify(meters=[m.as_dict() for m in meters])
def _list_users(source=None): """Return a list of user names. """ # TODO(jd) it might be better to return the real list of users that are # belonging to the project, but that's not provided by the storage # drivers for now if acl.get_limited_to_project(flask.request.headers): users = [flask.request.headers.get('X-User-id')] else: users = flask.request.storage_conn.get_users(source=source) return flask.jsonify(users=list(users))
def get_one(self, alarm_id): """Return one alarm.""" conn = pecan.request.storage_conn auth_project = acl.get_limited_to_project(pecan.request.headers) alarms = list(conn.get_alarms(alarm_id=alarm_id, project=auth_project)) # FIXME (flwang): Need to change this to return a 404 error code when # we get a release of WSME that supports it. if len(alarms) < 1: raise wsme.exc.ClientSideError(_("Unknown alarm")) return Alarm.from_db_model(alarms[0])
def _alarm(self): self.conn = pecan.request.storage_conn auth_project = acl.get_limited_to_project(pecan.request.headers) alarms = list( self.conn.get_alarms(alarm_id=self._id, project=auth_project)) # FIXME (flwang): Need to change this to return a 404 error code when # we get a release of WSME that supports it. if len(alarms) < 1: error = _("Unknown alarm") pecan.response.translatable_error = error raise wsme.exc.ClientSideError(error) return alarms[0]
def get_one(self, resource_id): """Retrieve details about one resource. :param resource_id: The UUID of the resource. """ authorized_project = acl.get_limited_to_project(pecan.request.headers) resources = list(pecan.request.storage_conn.get_resources(resource=resource_id, project=authorized_project)) # FIXME (flwang): Need to change this to return a 404 error code when # we get a release of WSME that supports it. if not resources: raise wsme.exc.InvalidInput("resource_id", resource_id, _("Unknown resource")) return Resource.from_db_and_links(resources[0], self._resource_links(resource_id))
def _alarm(self): self.conn = pecan.request.storage_conn auth_project = acl.get_limited_to_project(pecan.request.headers) alarms = list(self.conn.get_alarms(alarm_id=self._id, project=auth_project)) # FIXME (flwang): Need to change this to return a 404 error code when # we get a release of WSME that supports it. if len(alarms) < 1: error = _("Unknown alarm") pecan.response.translatable_error = error raise wsme.exc.ClientSideError(error) return alarms[0]
def list_meters_by_user(user): """Return a list of meters by user. :param user: The ID of the owning user. :param metadata.<key>: match on the metadata within the resource. (optional) """ rq = flask.request meters = rq.storage_conn.get_meters( user=user, project=acl.get_limited_to_project(rq.headers), metaquery=_get_metaquery(rq.args)) return flask.jsonify(meters=[m.as_dict() for m in meters])
def list_meters_by_user(user): """Return a list of meters by user. :param user: The ID of the owning user. :param metadata.<key>: match on the metadata within the resource. (optional) """ rq = flask.request meters = rq.storage_conn.get_meters(user=user, project=acl.get_limited_to_project( rq.headers), metaquery=_get_metaquery(rq.args)) return flask.jsonify(meters=[m.as_dict() for m in meters])
def history(self, q=[]): """Assembles the alarm history requested. :param q: Filter rules for the changes to be described. """ # allow history to be returned for deleted alarms, but scope changes # returned to those carried out on behalf of the auth'd tenant, to # avoid inappropriate cross-tenant visibility of alarm history auth_project = acl.get_limited_to_project(pecan.request.headers) conn = pecan.request.storage_conn kwargs = _query_to_kwargs(q, conn.get_alarm_changes, ['on_behalf_of']) return [AlarmChange.from_db_model(ac) for ac in conn.get_alarm_changes(self._id, auth_project, **kwargs)]
def list_all_resources(): """Return a list of all known resources. :param start_timestamp: Limits resources by last update time >= this value. (optional) :type start_timestamp: ISO date in UTC :param end_timestamp: Limits resources by last update time < this value. (optional) :type end_timestamp: ISO date in UTC :param metadata.<key>: match on the metadata within the resource. (optional) """ return _list_resources( project=acl.get_limited_to_project(flask.request.headers))
def _list_projects(source=None): """Return a list of project names. """ project = acl.get_limited_to_project(flask.request.headers) if project: if source: if project in flask.request.storage_conn.get_projects( source=source): projects = [project] else: projects = [] else: projects = [project] else: projects = flask.request.storage_conn.get_projects(source=source) return flask.jsonify(projects=list(projects))
def get_one(self, resource_id): """Retrieve details about one resource. :param resource_id: The UUID of the resource. """ authorized_project = acl.get_limited_to_project(pecan.request.headers) resources = list(pecan.request.storage_conn.get_resources( resource=resource_id, project=authorized_project)) # FIXME (flwang): Need to change this to return a 404 error code when # we get a release of WSME that supports it. if not resources: raise wsme.exc.InvalidInput("resource_id", resource_id, _("Unknown resource")) return Resource.from_db_and_links(resources[0], self._resource_links(resource_id))
def list_resources_by_user(user): """Return a list of resources owned by the user. :param user: The ID of the owning user. :param start_timestamp: Limits resources by last update time >= this value. (optional) :type start_timestamp: ISO date in UTC :param end_timestamp: Limits resources by last update time < this value. (optional) :type end_timestamp: ISO date in UTC :param metadata.<key>: match on the metadata within the resource. (optional) """ return _list_resources( user=user, project=acl.get_limited_to_project(flask.request.headers), )
def list_samples_by_user(user, meter): """Return a list of raw samples for the user. :param user: The ID of the user. :param meter: The name of the meter. :param start_timestamp: Limits samples by timestamp >= this value. (optional) :type start_timestamp: ISO date in UTC :param end_timestamp: Limits samples by timestamp < this value. (optional) :type end_timestamp: ISO date in UTC """ return _list_samples( user=user, meter=meter, project=acl.get_limited_to_project(flask.request.headers), )
def compute_resource_volume_sum(resource, meter): """Return the sum of samples for a meter. :param resource: The ID of the resource. :param meter: The name of the meter. :param start_timestamp: ISO-formatted string of the earliest time to include in the calculation. :param end_timestamp: ISO-formatted string of the latest time to include in the calculation. :param search_offset: Number of minutes before and after start and end timestamps to query. """ return _get_statistics( 'sum', meter=meter, resource=resource, project=acl.get_limited_to_project(flask.request.headers), )
def list_resources_by_source(source): """Return a list of resources for which a source is reporting data. :param source: The ID of the reporting source. :param start_timestamp: Limits resources by last update time >= this value. (optional) :type start_timestamp: ISO date in UTC :param end_timestamp: Limits resources by last update time < this value. (optional) :type end_timestamp: ISO date in UTC :param metadata.<key>: match on the metadata within the resource. (optional) """ return _list_resources( source=source, project=acl.get_limited_to_project(flask.request.headers), )
def put(self, alarm_id, data): """Modify an alarm.""" conn = pecan.request.storage_conn data.state_timestamp = wsme.Unset data.alarm_id = alarm_id auth_project = acl.get_limited_to_project(pecan.request.headers) alarms = list(conn.get_alarms(alarm_id=alarm_id, project=auth_project)) if len(alarms) < 1: raise wsme.exc.ClientSideError(_("Unknown alarm")) # merge the new values from kwargs into the current # alarm "alarm_in". alarm_in = alarms[0] kwargs = data.as_dict(storage.models.Alarm) for k, v in kwargs.iteritems(): setattr(alarm_in, k, v) if k == 'state': alarm_in.state_timestamp = timeutils.utcnow() alarm = conn.update_alarm(alarm_in) return Alarm.from_db_model(alarm)
def post(self, body): """Post a list of new Samples to Ceilometer. :param body: a list of samples within the request body. """ # Note: # 1) the above validate decorator seems to do nothing. # 2) the mandatory options seems to also do nothing. # 3) the body should already be in a list of Sample's samples = [Sample(**b) for b in body] now = timeutils.utcnow() auth_project = acl.get_limited_to_project(pecan.request.headers) def_source = pecan.request.cfg.sample_source def_project_id = pecan.request.headers.get('X-Project-Id') def_user_id = pecan.request.headers.get('X-User-Id') published_samples = [] for s in samples: if self._id != s.counter_name: raise wsme.exc.InvalidInput('counter_name', s.counter_name, 'should be %s' % self._id) if s.message_id: raise wsme.exc.InvalidInput('message_id', s.message_id, 'The message_id must not be set') if s.counter_type not in sample.TYPES: raise wsme.exc.InvalidInput('counter_type', s.counter_type, 'The counter type must be: ' + ', '.join(sample.TYPES)) s.user_id = (s.user_id or def_user_id) s.project_id = (s.project_id or def_project_id) s.source = '%s:%s' % (s.project_id, (s.source or def_source)) s.timestamp = (s.timestamp or now) if auth_project and auth_project != s.project_id: # non admin user trying to cross post to another project_id auth_msg = 'can not post samples to other projects' raise wsme.exc.InvalidInput('project_id', s.project_id, auth_msg) published_sample = sample.Sample( name=s.counter_name, type=s.counter_type, unit=s.counter_unit, volume=s.counter_volume, user_id=s.user_id, project_id=s.project_id, resource_id=s.resource_id, timestamp=s.timestamp.isoformat(), resource_metadata=s.resource_metadata, source=s.source) published_samples.append(published_sample) s.message_id = published_sample.id with pecan.request.pipeline_manager.publisher( context.get_admin_context()) as publisher: publisher(published_samples) # TODO(asalkeld) this is not ideal, it would be nice if the publisher # returned the created sample message with message id (or at least the # a list of message_ids). return samples
def post(self, body): """Post a list of new Samples to Ceilometer. :param body: a list of samples within the request body. """ # Note: # 1) the above validate decorator seems to do nothing. # 2) the mandatory options seems to also do nothing. # 3) the body should already be in a list of Sample's def get_consistent_source(): '''Find a source that can be applied across the sample group or raise InvalidInput if the sources are inconsistent. If all are None - use the configured counter_source If any sample has source set then the others must be the same or None. ''' source = None for s in samples: if source and s.source: if source != s.source: raise wsme.exc.InvalidInput('source', s.source, 'can not post Samples %s' % 'with different sources') if s.source and not source: source = s.source return source or pecan.request.cfg.counter_source samples = [Sample(**b) for b in body] now = timeutils.utcnow() auth_project = acl.get_limited_to_project(pecan.request.headers) source = get_consistent_source() for s in samples: if self._id != s.counter_name: raise wsme.exc.InvalidInput('counter_name', s.counter_name, 'should be %s' % self._id) if auth_project and auth_project != s.project_id: # non admin user trying to cross post to another project_id auth_msg = 'can not post samples to other projects' raise wsme.exc.InvalidInput('project_id', s.project_id, auth_msg) if s.timestamp is None or s.timestamp is wsme.Unset: s.timestamp = now s.source = '%s:%s' % (s.project_id, source) with pipeline.PublishContext( context.get_admin_context(), source, pecan.request.pipeline_manager.pipelines, ) as publisher: publisher([counter.Counter( name=s.counter_name, type=s.counter_type, unit=s.counter_unit, volume=s.counter_volume, user_id=s.user_id, project_id=s.project_id, resource_id=s.resource_id, timestamp=s.timestamp.isoformat(), resource_metadata=s.resource_metadata) for s in samples]) # TODO(asalkeld) this is not ideal, it would be nice if the publisher # returned the created sample message with message id (or at least the # a list of message_ids). return samples
def post(self, body): """Post a list of new Samples to Ceilometer. :param body: a list of samples within the request body. """ # Note: # 1) the above validate decorator seems to do nothing. # 2) the mandatory options seems to also do nothing. # 3) the body should already be in a list of Sample's samples = [Sample(**b) for b in body] now = timeutils.utcnow() auth_project = acl.get_limited_to_project(pecan.request.headers) def_source = pecan.request.cfg.sample_source def_project_id = pecan.request.headers.get('X-Project-Id') def_user_id = pecan.request.headers.get('X-User-Id') published_samples = [] for s in samples: if self._id != s.counter_name: raise wsme.exc.InvalidInput('counter_name', s.counter_name, 'should be %s' % self._id) if s.message_id: raise wsme.exc.InvalidInput('message_id', s.message_id, 'The message_id must not be set') s.user_id = (s.user_id or def_user_id) s.project_id = (s.project_id or def_project_id) s.source = '%s:%s' % (s.project_id, (s.source or def_source)) s.timestamp = (s.timestamp or now) if auth_project and auth_project != s.project_id: # non admin user trying to cross post to another project_id auth_msg = 'can not post samples to other projects' raise wsme.exc.InvalidInput('project_id', s.project_id, auth_msg) published_sample = sample.Sample( name=s.counter_name, type=s.counter_type, unit=s.counter_unit, volume=s.counter_volume, user_id=s.user_id, project_id=s.project_id, resource_id=s.resource_id, timestamp=s.timestamp.isoformat(), resource_metadata=s.resource_metadata, source=s.source) published_samples.append(published_sample) s.message_id = published_sample.id with pecan.request.pipeline_manager.publisher( context.get_admin_context()) as publisher: publisher(published_samples) # TODO(asalkeld) this is not ideal, it would be nice if the publisher # returned the created sample message with message id (or at least the # a list of message_ids). return samples
def check_authorized_project(project): authorized_project = acl.get_limited_to_project(flask.request.headers) if authorized_project and authorized_project != project: flask.abort(404)