diff --git a/ceilometer/api/controllers/v2/events.py b/ceilometer/api/controllers/v2/events.py index b314dde0..1b93ac7c 100644 --- a/ceilometer/api/controllers/v2/events.py +++ b/ceilometer/api/controllers/v2/events.py @@ -157,16 +157,19 @@ class Event(base.Base): ) -def _add_user_proj_filter(): - traits_filter = [] +def _build_rbac_query_filters(): + filters = {'t_filter': [], 'admin_proj': None} # Returns user_id, proj_id for non-admins user_id, proj_id = rbac.get_limited_to(pecan.request.headers) # If non-admin, filter events by user and project - if (user_id and proj_id): - traits_filter.append({"key": "project_id", "string": proj_id, - "op": "eq"}) - traits_filter.append({"key": "user_id", "string": user_id, "op": "eq"}) - return traits_filter + if user_id and proj_id: + filters['t_filter'].append({"key": "project_id", "string": proj_id, + "op": "eq"}) + filters['t_filter'].append({"key": "user_id", "string": user_id, + "op": "eq"}) + elif not user_id and not proj_id: + filters['admin_proj'] = pecan.request.headers.get('X-Project-Id') + return filters def _event_query_to_event_filter(q): @@ -176,7 +179,9 @@ def _event_query_to_event_filter(q): 'start_timestamp': None, 'end_timestamp': None } - traits_filter = _add_user_proj_filter() + filters = _build_rbac_query_filters() + traits_filter = filters['t_filter'] + admin_proj = filters['admin_proj'] for i in q: if not i.op: @@ -193,7 +198,8 @@ def _event_query_to_event_filter(q): traits_filter.append({"key": i.field, trait_type: i._get_value_as_type(), "op": i.op}) - return storage.EventFilter(traits_filter=traits_filter, **evt_model_filter) + return storage.EventFilter(traits_filter=traits_filter, + admin_proj=admin_proj, **evt_model_filter) class TraitsController(rest.RestController): @@ -279,8 +285,11 @@ class EventsController(rest.RestController): :param message_id: Message ID of the Event to be returned """ rbac.enforce("events:show", pecan.request) - t_filter = _add_user_proj_filter() + filters = _build_rbac_query_filters() + t_filter = filters['t_filter'] + admin_proj = filters['admin_proj'] event_filter = storage.EventFilter(traits_filter=t_filter, + admin_proj=admin_proj, message_id=message_id) events = [event for event in pecan.request.event_storage_conn.get_events(event_filter)] diff --git a/ceilometer/event/storage/impl_elasticsearch.py b/ceilometer/event/storage/impl_elasticsearch.py index 9a992ccc..ab7ee867 100644 --- a/ceilometer/event/storage/impl_elasticsearch.py +++ b/ceilometer/event/storage/impl_elasticsearch.py @@ -141,9 +141,10 @@ class Connection(base.Connection): q_args['doc_type'] = ev_filter.event_type if ev_filter.message_id: filters.append({'term': {'_id': ev_filter.message_id}}) - if ev_filter.traits_filter: + if ev_filter.traits_filter or ev_filter.admin_proj: trait_filters = [] - for t_filter in ev_filter.traits_filter: + or_cond = [] + for t_filter in ev_filter.traits_filter or []: value = None for val_type in ['integer', 'string', 'float', 'datetime']: if t_filter.get(val_type): @@ -164,9 +165,13 @@ class Connection(base.Connection): if t_filter.get('op') == 'ne': tf = {"not": tf} trait_filters.append(tf) + if ev_filter.admin_proj: + or_cond = [{'missing': {'field': 'project_id'}}, + {'term': {'project_id': ev_filter.admin_proj}}] filters.append( {'nested': {'path': 'traits', 'query': {'filtered': { - 'filter': {'bool': {'must': trait_filters}}}}}}) + 'filter': {'bool': {'must': trait_filters, + 'should': or_cond}}}}}}) q_args['body'] = {'query': {'filtered': {'filter': {'bool': {'must': filters}}}}} diff --git a/ceilometer/event/storage/impl_sqlalchemy.py b/ceilometer/event/storage/impl_sqlalchemy.py index a34831a5..e0497b42 100644 --- a/ceilometer/event/storage/impl_sqlalchemy.py +++ b/ceilometer/event/storage/impl_sqlalchemy.py @@ -265,6 +265,15 @@ class Connection(base.Connection): if trait_subq is not None: query = query.join(trait_subq, trait_subq.c.ev_id == models.Event.id) + if event_filter.admin_proj: + admin_q = session.query(models.TraitText).filter( + models.TraitText.key == 'project_id') + query = query.filter(sa.or_(~sa.exists().where( + models.Event.id == admin_q.subquery().c.event_id), + sa.and_( + models.TraitText.key == 'project_id', + models.TraitText.value == event_filter.admin_proj, + models.Event.id == models.TraitText.event_id))) if event_filter_conditions: query = query.filter(sa.and_(*event_filter_conditions)) @@ -274,7 +283,7 @@ class Connection(base.Connection): for (id_, generated, message_id, desc, raw) in query.add_columns( models.Event.generated, models.Event.message_id, - models.EventType.desc, models.Event.raw).all(): + models.EventType.desc, models.Event.raw).distinct().all(): event_list[id_] = api_models.Event(message_id, desc, generated, [], raw) # Query all traits related to events. diff --git a/ceilometer/storage/__init__.py b/ceilometer/storage/__init__.py index 7e15b0b3..5df4cec8 100644 --- a/ceilometer/storage/__init__.py +++ b/ceilometer/storage/__init__.py @@ -214,6 +214,7 @@ class EventFilter(object): :param end_timestamp: UTC end datetime (mandatory) :param event_type: the name of the event. None for all. :param message_id: the message_id of the event. None for all. + :param admin_proj: the project_id of admin role. None if non-admin user. :param traits_filter: the trait filter dicts, all of which are optional. This parameter is a list of dictionaries that specify trait values: @@ -228,12 +229,14 @@ class EventFilter(object): """ def __init__(self, start_timestamp=None, end_timestamp=None, - event_type=None, message_id=None, traits_filter=None): + event_type=None, message_id=None, traits_filter=None, + admin_proj=None): self.start_timestamp = utils.sanitize_timestamp(start_timestamp) self.end_timestamp = utils.sanitize_timestamp(end_timestamp) self.message_id = message_id self.event_type = event_type self.traits_filter = traits_filter or [] + self.admin_proj = admin_proj def __repr__(self): return ("