def get_resources(user=None, project=None, source=None, start_timestamp=None, start_timestamp_op=None, end_timestamp=None, end_timestamp_op=None, metaquery={}, resource=None): """Return an iterable of api_models.Resource instances :param user: Optional ID for user that owns the resource. :param project: Optional ID for project that owns the resource. :param source: Optional source filter. :param start_timestamp: Optional modified timestamp start range. :param start_timestamp_op: Optonal start time operator, like gt, ge. :param end_timestamp: Optional modified timestamp end range. :param end_timestamp_op: Optional end time operator, like lt, le. :param metaquery: Optional dict with metadata to match on. :param resource: Optional resource filter. """ session = sqlalchemy_session.get_session() query = session.query(Meter, ).group_by(Meter.resource_id) if user is not None: query = query.filter(Meter.user_id == user) if source is not None: query = query.filter(Meter.sources.any(id=source)) if start_timestamp: if start_timestamp_op == 'gt': query = query.filter(Meter.timestamp > start_timestamp) else: query = query.filter(Meter.timestamp >= start_timestamp) if end_timestamp: if end_timestamp_op == 'le': query = query.filter(Meter.timestamp <= end_timestamp) else: query = query.filter(Meter.timestamp < end_timestamp) if project is not None: query = query.filter(Meter.project_id == project) if resource is not None: query = query.filter(Meter.resource_id == resource) if metaquery: raise NotImplementedError('metaquery not implemented') for meter in query.all(): yield api_models.Resource( resource_id=meter.resource_id, project_id=meter.project_id, source=meter.sources[0].id, user_id=meter.user_id, metadata=meter.resource_metadata, meter=[ api_models.ResourceMeter( counter_name=m.counter_name, counter_type=m.counter_type, counter_unit=m.counter_unit, ) for m in meter.resource.meters ], )
def get_resources(self, user=None, project=None, source=None, start_timestamp=None, end_timestamp=None, metaquery={}, resource=None): """Return an iterable of models.Resource instances :param user: Optional ID for user that owns the resource. :param project: Optional ID for project that owns the resource. :param source: Optional source filter. :param start_timestamp: Optional modified timestamp start range. :param end_timestamp: Optional modified timestamp end range. :param metaquery: Optional dict with metadata to match on. :param resource: Optional resource filter. """ q = {} if user is not None: q['user_id'] = user if project is not None: q['project_id'] = project if source is not None: q['source'] = source if resource is not None: q['resource_id'] = resource # Add resource_ prefix so it matches the field in the db q.update(dict(('resource_' + k, v) for (k, v) in metaquery.iteritems())) # FIXME(dhellmann): This may not perform very well, # but doing any better will require changing the database # schema and that will need more thought than I have time # to put into it today. if start_timestamp or end_timestamp: # Look for resources matching the above criteria and with # samples in the time range we care about, then change the # resource query to return just those resources by id. ts_range = make_timestamp_range(start_timestamp, end_timestamp) if ts_range: q['timestamp'] = ts_range # FIXME(jd): We should use self.db.meter.group() and not use the # resource collection, but that's not supported by MIM, so it's not # easily testable yet. Since it was bugged before anyway, it's still # better for now. resource_ids = self.db.meter.find(q).distinct('resource_id') q = {'_id': {'$in': resource_ids}} for resource in self.db.resource.find(q): yield models.Resource( resource_id=resource['_id'], project_id=resource['project_id'], user_id=resource['user_id'], metadata=resource['metadata'], meter=[ models.ResourceMeter( counter_name=meter['counter_name'], counter_type=meter['counter_type'], counter_unit=meter['counter_unit'], ) for meter in resource['meter'] ], )
def test_get_resources(self): resources = list(self.conn.get_resources()) assert len(resources) == 4 for resource in resources: if resource.resource_id != 'resource-id': continue assert resource.resource_id == 'resource-id' assert resource.project_id == 'project-id' assert resource.user_id == 'user-id' assert resource.metadata['display_name'] == 'test-server' self.assertIn(models.ResourceMeter('instance', 'cumulative', ''), resource.meter) break else: assert False, 'Never found resource-id'
def get_resources(self, user=None, project=None, source=None, start_timestamp=None, end_timestamp=None, metaquery={}): """Return an iterable of models.Resource instances :param user: Optional ID for user that owns the resource. :param project: Optional ID for project that owns the resource. :param source: Optional source filter. :param start_timestamp: Optional modified timestamp start range. :param end_timestamp: Optional modified timestamp end range. """ q, start_row, end_row = make_query(user=user, project=project, source=source, start=start_timestamp, end=end_timestamp, require_meter=False) LOG.debug("q: %s" % q) # TODO implement metaquery support if len(metaquery) > 0: raise NotImplementedError('metaquery not implemented') resource_ids = {} g = self.meter.scan(filter=q, row_start=start_row, row_stop=end_row) for ignored, data in g: resource_ids[data['f:resource_id']] = data['f:resource_id'] q = make_query(user=user, project=project, source=source, query_only=True, require_meter=False) LOG.debug("q: %s" % q) for resource_id, data in self.resource.rows(resource_ids): yield models.Resource( resource_id=resource_id, project_id=data['f:project_id'], user_id=data['f:user_id'], metadata=json.loads(data['f:metadata']), meter=[ models.ResourceMeter(*(m[4:].split("!"))) for m in data if m.startswith('f:m_') ], )
def make_resource(data): """Transform HBase fields to Resource model.""" # convert HBase metadata e.g. f:r_display_name to display_name data['f:metadata'] = dict((k[4:], v) for k, v in data.iteritems() if k.startswith('f:r_')) return models.Resource( resource_id=data['f:resource_id'], project_id=data['f:project_id'], source=data['f:source'], user_id=data['f:user_id'], metadata=data['f:metadata'], meter=[ models.ResourceMeter(*(m[4:].split("!"))) for m in data if m.startswith('f:m_') ], )
def make_resource(data, first_ts, last_ts, meter_refs): """Transform HBase fields to Resource model.""" # convert HBase metadata e.g. f:r_display_name to display_name data['f:metadata'] = _metadata_from_document(data) return models.Resource( resource_id=data['f:resource_id'], first_sample_timestamp=first_ts, last_sample_timestamp=last_ts, project_id=data['f:project_id'], source=data['f:source'], user_id=data['f:user_id'], metadata=data['f:metadata'], meter=[ models.ResourceMeter(*(m.split("!"))) for m in meter_refs ], )
def test_get_resources(self): msgs_sources = [msg['source'] for msg in self.msgs] resources = list(self.conn.get_resources()) self.assertEqual(len(resources), 9) for resource in resources: if resource.resource_id != 'resource-id': continue self.assertEqual(resource.first_sample_timestamp, None) self.assertEqual(resource.last_sample_timestamp, None) assert resource.resource_id == 'resource-id' assert resource.project_id == 'project-id' self.assertIn(resource.source, msgs_sources) assert resource.user_id == 'user-id' assert resource.metadata['display_name'] == 'test-server' self.assertIn(models.ResourceMeter('instance', 'cumulative', ''), resource.meter) break else: assert False, 'Never found resource-id'
def get_resources(self, user=None, project=None, source=None, start_timestamp=None, start_timestamp_op=None, end_timestamp=None, end_timestamp_op=None, metaquery={}, resource=None, limit=None, marker_pairs=None, sort_key=None, sort_dir=None): """Return an iterable of models.Resource instances :param user: Optional ID for user that owns the resource. :param project: Optional ID for project that owns the resource. :param source: Optional source filter. :param start_timestamp: Optional modified timestamp start range. :param start_timestamp_op: Optional start time operator, like gt, ge. :param end_timestamp: Optional modified timestamp end range. :param end_timestamp_op: Optional end time operator, like lt, le. :param metaquery: Optional dict with metadata to match on. :param resource: Optional resource filter. :param limit: Number of documents should be returned. :param marker_pairs: Attribute-value pairs to identify the last item of the previous page. :param sort_key: Attribute by which results be sorted. :param sort_dir: Direction with which results be sorted(asc, desc). """ if marker_pairs: raise NotImplementedError( "Cannot use marker pairs in resource listing, not implemented") q = {} if user is not None: q['user_id'] = user if project is not None: q['project_id'] = project if source is not None: q['source'] = source if resource is not None: q['resource_id'] = resource # Add resource_ prefix so it matches the field in the db q.update(dict( ('resource_' + k, v) for (k, v) in metaquery.iteritems())) # FIXME(dhellmann): This may not perform very well, # but doing any better will require changing the database # schema and that will need more thought than I have time # to put into it today. if start_timestamp or end_timestamp: # Look for resources matching the above criteria and with # samples in the time range we care about, then change the # resource query to return just those resources by id. ts_range = make_timestamp_range(start_timestamp, end_timestamp, start_timestamp_op, end_timestamp_op) if ts_range: q['timestamp'] = ts_range sort_keys = base._handle_sort_key('resource', sort_key) sort_instruction, query = self._build_paginate_query( None, sort_keys, sort_dir) q.update(query) aggregate = self.db.meter.aggregate([ { "$match": q }, { "$sort": dict(sort_instruction) }, { "$group": { "_id": "$resource_id", "user_id": { "$first": "$user_id" }, "project_id": { "$first": "$project_id" }, "source": { "$first": "$source" }, "first_sample_timestamp": { "$min": "$timestamp" }, "last_sample_timestamp": { "$max": "$timestamp" }, "metadata": { "$first": "$resource_metadata" }, "meters_name": { "$push": "$counter_name" }, "meters_type": { "$push": "$counter_type" }, "meters_unit": { "$push": "$counter_unit" }, } }, ]) for result in aggregate['result']: if limit is not None: if limit == 0: break limit -= 1 yield models.Resource( resource_id=result['_id'], user_id=result['user_id'], project_id=result['project_id'], first_sample_timestamp=result['first_sample_timestamp'], last_sample_timestamp=result['last_sample_timestamp'], source=result['source'], metadata=result['metadata'], meter=[ models.ResourceMeter( counter_name=m_n, counter_type=m_t, counter_unit=m_u, ) for m_n, m_u, m_t in zip(result['meters_name'], result['meters_unit'], result['meters_type']) ], )
def get_resources(self, user=None, project=None, source=None, start_timestamp=None, start_timestamp_op=None, end_timestamp=None, end_timestamp_op=None, metaquery={}, resource=None, pagination=None): """Return an iterable of models.Resource instances :param user: Optional ID for user that owns the resource. :param project: Optional ID for project that owns the resource. :param source: Optional source filter. :param start_timestamp: Optional modified timestamp start range. :param start_timestamp_op: Optional start time operator, like gt, ge. :param end_timestamp: Optional modified timestamp end range. :param end_timestamp_op: Optional end time operator, like lt, le. :param metaquery: Optional dict with metadata to match on. :param resource: Optional resource filter. :param pagination: Optional pagination query. """ if pagination: raise NotImplementedError(_('Pagination not implemented')) q = {} if user is not None: q['user_id'] = user if project is not None: q['project_id'] = project if source is not None: q['source'] = source if resource is not None: q['resource_id'] = resource # Add resource_ prefix so it matches the field in the db q.update(dict(('resource_' + k, v) for (k, v) in metaquery.iteritems())) if start_timestamp or end_timestamp: # Look for resources matching the above criteria and with # samples in the time range we care about, then change the # resource query to return just those resources by id. ts_range = make_timestamp_range(start_timestamp, end_timestamp, start_timestamp_op, end_timestamp_op) if ts_range: q['timestamp'] = ts_range sort_keys = base._handle_sort_key('resource', 'timestamp') sort_keys.insert(0, 'resource_id') sort_instructions = self._build_sort_instructions(sort_keys=sort_keys, sort_dir='desc') resource = lambda x: x['resource_id'] meters = self.db.meter.find(q, sort=sort_instructions) for resource_id, r_meters in itertools.groupby(meters, key=resource): resource_meters = [] # Because we have to know first/last timestamp, and we need a full # list of references to the resource's meters, we need a tuple # here. r_meters = tuple(r_meters) for meter in r_meters: resource_meters.append(models.ResourceMeter( counter_name=meter['counter_name'], counter_type=meter['counter_type'], counter_unit=meter.get('counter_unit', '')) ) latest_meter = r_meters[0] last_ts = latest_meter['timestamp'] first_ts = r_meters[-1]['timestamp'] yield models.Resource(resource_id=latest_meter['resource_id'], project_id=latest_meter['project_id'], first_sample_timestamp=first_ts, last_sample_timestamp=last_ts, source=latest_meter['source'], user_id=latest_meter['user_id'], metadata=latest_meter['resource_metadata'], meter=resource_meters)
def get_resources(user=None, project=None, source=None, start_timestamp=None, start_timestamp_op=None, end_timestamp=None, end_timestamp_op=None, metaquery={}, resource=None, pagination=None): """Return an iterable of api_models.Resource instances :param user: Optional ID for user that owns the resource. :param project: Optional ID for project that owns the resource. :param source: Optional source filter. :param start_timestamp: Optional modified timestamp start range. :param start_timestamp_op: Optonal start time operator, like gt, ge. :param end_timestamp: Optional modified timestamp end range. :param end_timestamp_op: Optional end time operator, like lt, le. :param metaquery: Optional dict with metadata to match on. :param resource: Optional resource filter. :param pagination: Optional pagination query. """ # We probably want to raise these early, since we don't know from here # if they will be handled. We don't want extra wait or work for it to # just fail. if pagination: raise NotImplementedError(_('Pagination not implemented')) # (thomasm) We need to get the max timestamp first, since that's the # most accurate. We also need to filter down in the subquery to # constrain what we have to JOIN on later. session = sqlalchemy_session.get_session() ts_subquery = session.query( Meter.resource_id, func.max(Meter.timestamp).label("max_ts"), func.min(Meter.timestamp).label("min_ts")).group_by( Meter.resource_id) # Here are the basic 'eq' operation filters for the sample data. for column, value in [(Meter.resource_id, resource), (Meter.user_id, user), (Meter.project_id, project)]: if value: ts_subquery = ts_subquery.filter(column == value) if source: ts_subquery = ts_subquery.filter(Meter.sources.any(id=source)) if metaquery: ts_subquery = apply_metaquery_filter(session, ts_subquery, metaquery) # Here we limit the samples being used to a specific time period, # if requested. if start_timestamp: if start_timestamp_op == 'gt': ts_subquery = ts_subquery.filter( Meter.timestamp > start_timestamp) else: ts_subquery = ts_subquery.filter( Meter.timestamp >= start_timestamp) if end_timestamp: if end_timestamp_op == 'le': ts_subquery = ts_subquery.filter( Meter.timestamp <= end_timestamp) else: ts_subquery = ts_subquery.filter( Meter.timestamp < end_timestamp) ts_subquery = ts_subquery.subquery() # Now we need to get the max Meter.id out of the leftover results, to # break any ties. agg_subquery = session.query( func.max(Meter.id).label("max_id"), ts_subquery).filter( Meter.resource_id == ts_subquery.c.resource_id, Meter.timestamp == ts_subquery.c.max_ts).group_by( ts_subquery.c.resource_id, ts_subquery.c.max_ts, ts_subquery.c.min_ts).subquery() query = session.query( Meter, agg_subquery.c.min_ts, agg_subquery.c.max_ts).filter(Meter.id == agg_subquery.c.max_id) for meter, first_ts, last_ts in query.all(): yield api_models.Resource( resource_id=meter.resource_id, project_id=meter.project_id, first_sample_timestamp=first_ts, last_sample_timestamp=last_ts, source=meter.sources[0].id, user_id=meter.user_id, metadata=meter.resource_metadata, meter=[ api_models.ResourceMeter( counter_name=m.counter_name, counter_type=m.counter_type, counter_unit=m.counter_unit, ) for m in meter.resource.meters ], )
def get_resources(self, user=None, project=None, source=None, start_timestamp=None, start_timestamp_op=None, end_timestamp=None, end_timestamp_op=None, metaquery={}, resource=None, limit=None, marker_pairs=None, sort_key=None, sort_dir=None): """Return an iterable of models.Resource instances :param user: Optional ID for user that owns the resource. :param project: Optional ID for project that owns the resource. :param source: Optional source filter. :param start_timestamp: Optional modified timestamp start range. :param start_timestamp_op: Optional start time operator, like gt, ge. :param end_timestamp: Optional modified timestamp end range. :param end_timestamp_op: Optional end time operator, like lt, le. :param metaquery: Optional dict with metadata to match on. :param resource: Optional resource filter. :param limit: Number of documents should be returned. :param marker_pairs: Attribute-value pairs to identify the last item of the previous page. :param sort_key: Attribute by which results be sorted. :param sort_dir: Direction with which results be sorted(asc, desc). """ q = {} if user is not None: q['user_id'] = user if project is not None: q['project_id'] = project if source is not None: q['source'] = source if resource is not None: q['resource_id'] = resource # Add resource_ prefix so it matches the field in the db q.update(dict(('resource_' + k, v) for (k, v) in metaquery.iteritems())) # FIXME(dhellmann): This may not perform very well, # but doing any better will require changing the database # schema and that will need more thought than I have time # to put into it today. if start_timestamp or end_timestamp: # Look for resources matching the above criteria and with # samples in the time range we care about, then change the # resource query to return just those resources by id. ts_range = make_timestamp_range(start_timestamp, end_timestamp, start_timestamp_op, end_timestamp_op) if ts_range: q['timestamp'] = ts_range marker = self._get_marker(self.db.resource, marker_pairs=marker_pairs) sort_keys = base._handle_sort_key('resource', sort_key) # FIXME(jd): We should use self.db.meter.group() and not use the # resource collection, but that's not supported by MIM, so it's not # easily testable yet. Since it was bugged before anyway, it's still # better for now. resource_ids = self.db.meter.find(q).distinct('resource_id') q = {'_id': {'$in': resource_ids}} for resource in self.paginate_query(q, self.db.resource, limit=limit, marker=marker, sort_keys=sort_keys, sort_dir=sort_dir): yield models.Resource( resource_id=resource['_id'], project_id=resource['project_id'], source=resource['source'], user_id=resource['user_id'], metadata=resource['metadata'], meter=[ models.ResourceMeter( counter_name=meter['counter_name'], counter_type=meter['counter_type'], counter_unit=meter.get('counter_unit', ''), ) for meter in resource['meter'] ], )
def get_resources(self, user=None, project=None, source=None, start_timestamp=None, start_timestamp_op=None, end_timestamp=None, end_timestamp_op=None, metaquery={}, resource=None, pagination=None): """Return an iterable of models.Resource instances :param user: Optional ID for user that owns the resource. :param project: Optional ID for project that owns the resource. :param source: Optional source filter. :param start_timestamp: Optional modified timestamp start range. :param start_timestamp_op: Optional start time operator, like gt, ge. :param end_timestamp: Optional modified timestamp end range. :param end_timestamp_op: Optional end time operator, like lt, le. :param metaquery: Optional dict with metadata to match on. :param resource: Optional resource filter. :param pagination: Optional pagination query. """ if pagination: raise NotImplementedError(_('Pagination not implemented')) q = {} if user is not None: q['user_id'] = user if project is not None: q['project_id'] = project if source is not None: q['source'] = source if resource is not None: q['resource_id'] = resource # Add resource_ prefix so it matches the field in the db q.update(dict(('resource_' + k, v) for (k, v) in metaquery.iteritems())) # FIXME(dhellmann): This may not perform very well, # but doing any better will require changing the database # schema and that will need more thought than I have time # to put into it today. if start_timestamp or end_timestamp: # Look for resources matching the above criteria and with # samples in the time range we care about, then change the # resource query to return just those resources by id. ts_range = make_timestamp_range(start_timestamp, end_timestamp, start_timestamp_op, end_timestamp_op) if ts_range: q['timestamp'] = ts_range sort_keys = base._handle_sort_key('resource') sort_instructions = self._build_sort_instructions(sort_keys)[0] aggregate = self.db.meter.aggregate([ {"$match": q}, {"$sort": dict(sort_instructions)}, {"$group": { "_id": "$resource_id", "user_id": {"$first": "$user_id"}, "project_id": {"$first": "$project_id"}, "source": {"$first": "$source"}, "first_sample_timestamp": {"$min": "$timestamp"}, "last_sample_timestamp": {"$max": "$timestamp"}, "metadata": {"$first": "$resource_metadata"}, "meters_name": {"$push": "$counter_name"}, "meters_type": {"$push": "$counter_type"}, "meters_unit": {"$push": "$counter_unit"}, }}, ]) for result in aggregate['result']: yield models.Resource( resource_id=result['_id'], user_id=result['user_id'], project_id=result['project_id'], first_sample_timestamp=result['first_sample_timestamp'], last_sample_timestamp=result['last_sample_timestamp'], source=result['source'], metadata=result['metadata'], meter=[ models.ResourceMeter( counter_name=m_n, counter_type=m_t, counter_unit=m_u, ) for m_n, m_u, m_t in zip(result['meters_name'], result['meters_unit'], result['meters_type']) ], )
def get_resources(self, user=None, project=None, source=None, start_timestamp=None, start_timestamp_op=None, end_timestamp=None, end_timestamp_op=None, metaquery={}, resource=None, pagination=None): """Return an iterable of models.Resource instances :param user: Optional ID for user that owns the resource. :param project: Optional ID for project that owns the resource. :param source: Optional source filter. :param start_timestamp: Optional modified timestamp start range. :param start_timestamp_op: Optional start time operator, like gt, ge. :param end_timestamp: Optional modified timestamp end range. :param end_timestamp_op: Optional end time operator, like lt, le. :param metaquery: Optional dict with metadata to match on. :param resource: Optional resource filter. :param pagination: Optional pagination query. """ if pagination: raise NotImplementedError(_('Pagination not implemented')) q = {} if user is not None: q['user_id'] = user if project is not None: q['project_id'] = project if source is not None: q['source'] = source if resource is not None: q['resource_id'] = resource # Add resource_ prefix so it matches the field in the db q.update(dict( ('resource_' + k, v) for (k, v) in metaquery.iteritems())) if start_timestamp or end_timestamp: # Look for resources matching the above criteria and with # samples in the time range we care about, then change the # resource query to return just those resources by id. ts_range = make_timestamp_range(start_timestamp, end_timestamp, start_timestamp_op, end_timestamp_op) if ts_range: q['timestamp'] = ts_range # FIXME(jd): We should use self.db.meter.group() and not use the # resource collection, but that's not supported by MIM, so it's not # easily testable yet. Since it was bugged before anyway, it's still # better for now. resource_ids = self.db.meter.find(q).distinct('resource_id') q = {'_id': {'$in': resource_ids}} for resource in self.db.resource.find(q): yield models.Resource( resource_id=resource['_id'], project_id=resource['project_id'], first_sample_timestamp=None, last_sample_timestamp=None, source=resource['source'], user_id=resource['user_id'], metadata=resource['metadata'], meter=[ models.ResourceMeter( counter_name=meter['counter_name'], counter_type=meter['counter_type'], counter_unit=meter.get('counter_unit', ''), ) for meter in resource['meter'] ], )