コード例 #1
0
    def summary(self,
                begin=None,
                end=None,
                tenant_id=None,
                service=None,
                groupby=None,
                all_tenants=False):
        """Return the summary to pay for a given period.

        """
        if not begin:
            begin = ck_utils.get_month_start()
        if not end:
            end = ck_utils.get_next_month()

        if all_tenants:
            tenant_id = None
        else:
            tenant_context = pecan.request.context.tenant
            tenant_id = tenant_context if not tenant_id else tenant_id
        policy.authorize(pecan.request.context, 'report:get_summary',
                         {"tenant_id": tenant_id})
        storage = pecan.request.storage_backend

        summarymodels = []
        results = storage.get_total(begin,
                                    end,
                                    tenant_id,
                                    service,
                                    groupby=groupby)
        for result in results:
            summarymodel = report_models.SummaryModel(**result)
            summarymodels.append(summarymodel)

        return report_models.SummaryCollectionModel(summary=summarymodels)
コード例 #2
0
    def get(self,
            offset=0,
            limit=100,
            scope_id=None,
            scope_key=None,
            fetcher=None,
            collector=None):

        policy.authorize(
            flask.request.context, 'scope:get_state',
            {'tenant_id': scope_id or flask.request.context.project_id})
        results = storage_state.StateManager().get_all(
            identifier=scope_id,
            scope_key=scope_key,
            fetcher=fetcher,
            collector=collector,
            offset=offset,
            limit=limit,
        )
        if len(results) < 1:
            raise http_exceptions.NotFound(
                "No resource found for provided filters.")
        return {
            'results': [{
                'scope_id': r.identifier,
                'scope_key': r.scope_key,
                'fetcher': r.fetcher,
                'collector': r.collector,
                'state': str(r.state),
            } for r in results]
        }
コード例 #3
0
    def get(self,
            groupby=None,
            filters={},
            begin=None,
            end=None,
            offset=0,
            limit=100):
        policy.authorize(flask.request.context, 'summary:get_summary',
                         {'tenant_id': flask.request.context.project_id})
        begin = begin or utils.get_month_start()
        end = end or utils.get_next_month()

        if not flask.request.context.is_admin:
            filters['project_id'] = flask.request.context.project_id

        total = self._storage.total(
            begin=begin,
            end=end,
            groupby=groupby,
            filters=filters,
            offset=offset,
            limit=limit,
            paginate=True,
        )
        columns = []
        if len(total['results']) > 0:
            columns = list(total['results'][0].keys())

        return {
            'total': total['total'],
            'columns': columns,
            'results': [list(res.values()) for res in total['results']]
        }
コード例 #4
0
ファイル: storage.py プロジェクト: woraser/cloudkitty
    def get_all(self,
                begin=None,
                end=None,
                tenant_id=None,
                resource_type=None):
        """Return a list of rated resources for a time period and a tenant.

        :param begin: Start of the period
        :param end: End of the period
        :param tenant_id: UUID of the tenant to filter on.
        :param resource_type: Type of the resource to filter on.
        :return: Collection of DataFrame objects.
        """

        policy.authorize(pecan.request.context, 'storage:list_data_frames', {})

        scope_key = CONF.collect.scope_key
        backend = pecan.request.storage_backend
        dataframes = []
        group_filters = {scope_key: tenant_id} if tenant_id else None

        if begin:
            begin = ck_utils.dt2ts(begin)
        if end:
            end = ck_utils.dt2ts(end)
        try:
            resp = backend.retrieve(begin,
                                    end,
                                    group_filters=group_filters,
                                    metric_types=resource_type,
                                    paginate=False)
        except storage.NoTimeFrame:
            return storage_models.DataFrameCollection(dataframes=[])
        for frame in resp['dataframes']:
            for service, data_list in frame['usage'].items():
                frame_tenant = None
                resources = []
                for data in data_list:
                    # This means we use a v1 storage backend
                    if 'desc' in data.keys():
                        desc = data['desc']
                    else:
                        desc = data['metadata'].copy()
                        desc.update(data.get('groupby', {}))
                    price = decimal.Decimal(str(data['rating']['price']))
                    resource = storage_models.RatedResource(
                        service=service,
                        desc=desc,
                        volume=data['vol']['qty'],
                        rating=price)
                    if frame_tenant is None:
                        frame_tenant = desc[scope_key]
                    resources.append(resource)
                dataframe = storage_models.DataFrame(
                    begin=ck_utils.iso2dt(frame['period']['begin']),
                    end=ck_utils.iso2dt(frame['period']['end']),
                    tenant_id=frame_tenant,
                    resources=resources)
                dataframes.append(dataframe)
        return storage_models.DataFrameCollection(dataframes=dataframes)
コード例 #5
0
    def _route(self, args, request):
        try:
            policy.authorize(request.context, 'rating:module_config', {})
        except policy.PolicyNotAuthorized as e:
            pecan.abort(403, six.text_type(e))

        return super(RatingRestControllerBase, self)._route(args, request)
コード例 #6
0
    def total(self,
              begin=None,
              end=None,
              tenant_id=None,
              service=None,
              all_tenants=False):
        """Return the amount to pay for a given period.

        """
        if not begin:
            begin = ck_utils.get_month_start()
        if not end:
            end = ck_utils.get_next_month()

        if all_tenants:
            tenant_id = None
        else:
            tenant_context = pecan.request.context.tenant
            tenant_id = tenant_context if not tenant_id else tenant_id
        policy.authorize(pecan.request.context, 'report:get_total',
                         {"tenant_id": tenant_id})

        storage = pecan.request.storage_backend
        # FIXME(sheeprine): We should filter on user id.
        # Use keystone token information by default but make it overridable and
        # enforce it by policy engine
        total = storage.get_total(begin, end, tenant_id, service)

        # TODO(Aaron): `get_total` return a list of dict,
        # Get value of rate from index[0]
        total = total[0].get('rate', decimal.Decimal('0'))
        return total if total else decimal.Decimal('0')
コード例 #7
0
ファイル: test_policy.py プロジェクト: woraser/cloudkitty
 def test_ignore_case_role_check(self):
     lowercase_action = "test:lowercase_admin"
     uppercase_action = "test:uppercase_admin"
     admin_context = context.RequestContext('admin',
                                            'fake',
                                            roles=['AdMiN'])
     policy.authorize(admin_context, lowercase_action, self.target)
     policy.authorize(admin_context, uppercase_action, self.target)
コード例 #8
0
    def reload_modules(self):
        """Trigger a rating module list reload.

        """
        policy.authorize(pecan.request.context, 'rating:module_config', {})
        self.modules.reload_extensions()
        self.module_config.reload_extensions()
        self.module_config.expose_modules()
コード例 #9
0
    def post(self,
             scope_id,
             scope_key=None,
             fetcher=None,
             collector=None,
             active=None):

        policy.authorize(
            flask.request.context, 'scope:post_state',
            {'tenant_id': scope_id or flask.request.context.project_id})
        results = self._storage_state.get_all(identifier=scope_id)

        if len(results) >= 1:
            LOG.debug(
                "There is already a scope with ID [%s], "
                "scopes found: [%s].", scope_id, results)
            raise http_exceptions.NotFound("Cannot create a scope with an "
                                           "already existing scope_id: %s." %
                                           scope_id)

        LOG.debug(
            "Creating storage scope with data: [scope_id=%s, "
            "scope_key=%s, fetcher=%s, collector=%s, active=%s].", scope_id,
            scope_key, fetcher, collector, active)

        self._storage_state.create_scope(scope_id,
                                         None,
                                         fetcher=fetcher,
                                         collector=collector,
                                         scope_key=scope_key,
                                         active=active)

        storage_scopes = self._storage_state.get_all(identifier=scope_id)

        update_storage_scope = storage_scopes[0]
        last_processed_timestamp = None
        if update_storage_scope.last_processed_timestamp.isoformat():
            last_processed_timestamp =\
                update_storage_scope.last_processed_timestamp.isoformat()

        return {
            'scope_id':
            update_storage_scope.identifier,
            'scope_key':
            update_storage_scope.scope_key,
            'fetcher':
            update_storage_scope.fetcher,
            'collector':
            update_storage_scope.collector,
            'state':
            last_processed_timestamp,
            'last_processed_timestamp':
            last_processed_timestamp,
            'active':
            update_storage_scope.active,
            'scope_activation_toggle_date':
            update_storage_scope.scope_activation_toggle_date.isoformat()
        }
コード例 #10
0
    def put(self,
            all_scopes=False,
            scope_id=None,
            scope_key=None,
            fetcher=None,
            collector=None,
            last_processed_timestamp=None,
            state=None):

        policy.authorize(
            flask.request.context, 'scope:reset_state',
            {'project_id': scope_id or flask.request.context.project_id})

        if not all_scopes and scope_id is None:
            raise http_exceptions.BadRequest(
                "Either all_scopes or a scope_id should be specified.")

        if not state and not last_processed_timestamp:
            raise http_exceptions.BadRequest(
                "Variables 'state' and 'last_processed_timestamp' cannot be "
                "empty/None. We expect at least one of them.")
        if state:
            LOG.warning("The use of 'state' variable is deprecated, and will "
                        "be removed in the next upcomming release. You should "
                        "consider using 'last_processed_timestamp' variable.")

        results = self._storage_state.get_all(
            identifier=scope_id,
            scope_key=scope_key,
            fetcher=fetcher,
            collector=collector,
        )

        if len(results) < 1:
            raise http_exceptions.NotFound(
                "No resource found for provided filters.")

        serialized_results = [{
            'scope_id': r.identifier,
            'scope_key': r.scope_key,
            'fetcher': r.fetcher,
            'collector': r.collector,
        } for r in results]

        if not last_processed_timestamp:
            last_processed_timestamp = state
        self._client.cast({},
                          'reset_state',
                          res_data={
                              'scopes':
                              serialized_results,
                              'last_processed_timestamp':
                              last_processed_timestamp.isoformat()
                          })

        return {}, 202
コード例 #11
0
    def get(self, name):
        """Query the enable state of a collector.

        :param name: Name of the collector.
        :return: State of the collector.
        """
        policy.authorize(pecan.request.context, 'collector:get_state', {})
        enabled = self._db.get_state('collector_{}'.format(name))
        collector = collector_models.CollectorInfos(name=name, enabled=enabled)
        return collector
コード例 #12
0
ファイル: collector.py プロジェクト: yashodhank/cloudkitty
    def delete(self, service):
        """Delete a service to collector mapping.

        :param service: Name of the service to filter on.
        """
        policy.authorize(pecan.request.context, 'collector:manage_mapping', {})
        try:
            self._db.delete_mapping(service)
        except db_api.NoSuchMapping as e:
            pecan.abort(404, six.text_type(e))
コード例 #13
0
ファイル: collector.py プロジェクト: yashodhank/cloudkitty
    def post(self, collector, service):
        """Create a service to collector mapping.

        :param collector: Name of the collector to apply mapping on.
        :param service: Name of the service to apply mapping on.
        """
        policy.authorize(pecan.request.context, 'collector:manage_mapping', {})
        new_mapping = self._db.set_mapping(service, collector)
        return collector_models.ServiceToCollectorMapping(
            service=new_mapping.service, collector=new_mapping.collector)
コード例 #14
0
ファイル: example.py プロジェクト: Allen-labs/cloudkitty
 def post(self, fruit=None):
     policy.authorize(flask.request.context, 'example:submit_fruit', {})
     if not fruit:
         raise http_exceptions.BadRequest('You must submit a fruit', )
     if fruit not in ['banana', 'strawberry']:
         raise http_exceptions.Forbidden(
             'You submitted a forbidden fruit', )
     return {
         'message': 'Your fruit is a ' + fruit,
     }
コード例 #15
0
ファイル: storage.py プロジェクト: musaqib/cloudkitty
    def get_all(self, begin=None, end=None, tenant_id=None,
                resource_type=None):
        """Return a list of rated resources for a time period and a tenant.

        :param begin: Start of the period
        :param end: End of the period
        :param tenant_id: UUID of the tenant to filter on.
        :param resource_type: Type of the resource to filter on.
        :return: Collection of DataFrame objects.
        """

        project_id = tenant_id or pecan.request.context.project_id
        policy.authorize(pecan.request.context, 'storage:list_data_frames', {
            'tenant_id': project_id,
        })

        scope_key = CONF.collect.scope_key
        backend = pecan.request.storage_backend
        dataframes = []
        filters = {scope_key: tenant_id} if tenant_id else None

        try:
            resp = backend.retrieve(
                begin, end,
                filters=filters,
                metric_types=resource_type,
                paginate=False)
        except storage.NoTimeFrame:
            return storage_models.DataFrameCollection(dataframes=[])
        for frame in resp['dataframes']:
            frame_tenant = None
            for type_, points in frame.itertypes():
                resources = []
                for point in points:
                    resource = storage_models.RatedResource(
                        service=type_,
                        desc=point.desc,
                        volume=point.qty,
                        rating=point.price)
                    if frame_tenant is None:
                        # NOTE(jferrieu): Since DataFrame/DataPoint
                        # implementation patch we cannot guarantee
                        # anymore that a DataFrame does contain a scope_id
                        # therefore the __UNDEF__ default value has been
                        # retained to maintain backward compatibility
                        # if it would occur being absent
                        frame_tenant = point.desc.get(scope_key, '__UNDEF__')
                    resources.append(resource)
                dataframe = storage_models.DataFrame(
                    begin=tzutils.local_to_utc(frame.start, naive=True),
                    end=tzutils.local_to_utc(frame.end, naive=True),
                    tenant_id=frame_tenant,
                    resources=resources)
                dataframes.append(dataframe)
        return storage_models.DataFrameCollection(dataframes=dataframes)
コード例 #16
0
    def delete(self, service):
        """Delete a service to collector mapping.

        :param service: Name of the service to filter on.
        """
        LOG.warning("Collector mappings are deprecated and shouldn't be used.")
        policy.authorize(pecan.request.context, 'collector:manage_mapping', {})
        try:
            self._db.delete_mapping(service)
        except db_api.NoSuchMapping as e:
            pecan.abort(404, e.args[0])
コード例 #17
0
    def put(self, name, infos):
        """Set the enable state of a collector.

        :param name: Name of the collector.
        :param infos: New state informations of the collector.
        :return: State of the collector.
        """
        policy.authorize(pecan.request.context, 'collector:update_state', {})
        enabled = self._db.set_state('collector_{}'.format(name),
                                     infos.enabled)
        collector = collector_models.CollectorInfos(name=name, enabled=enabled)
        return collector
コード例 #18
0
ファイル: collector.py プロジェクト: yashodhank/cloudkitty
    def get_one(self, service):
        """Return a service to collector mapping.

        :param service: Name of the service to filter on.
        """
        policy.authorize(pecan.request.context, 'collector:get_mapping', {})
        try:
            mapping = self._db.get_mapping(service)
            return collector_models.ServiceToCollectorMapping(
                **mapping.as_dict())
        except db_api.NoSuchMapping as e:
            pecan.abort(404, six.text_type(e))
コード例 #19
0
    def patch(self,
              scope_id,
              scope_key=None,
              fetcher=None,
              collector=None,
              active=None):

        policy.authorize(
            flask.request.context, 'scope:patch_state',
            {'tenant_id': scope_id or flask.request.context.project_id})
        results = self._storage_state.get_all(identifier=scope_id, active=None)

        if len(results) < 1:
            raise http_exceptions.NotFound(
                "No resource found for provided filters.")

        if len(results) > 1:
            LOG.debug(
                "Too many resources found with the same scope_id [%s], "
                "scopes found: [%s].", scope_id, results)
            raise http_exceptions.NotFound("Too many resources found with "
                                           "the same scope_id: %s." % scope_id)

        scope_to_update = results[0]
        LOG.debug("Executing update of storage scope: [%s].", scope_to_update)

        self._storage_state.update_storage_scope(scope_to_update,
                                                 scope_key=scope_key,
                                                 fetcher=fetcher,
                                                 collector=collector,
                                                 active=active)

        storage_scopes = self._storage_state.get_all(identifier=scope_id,
                                                     active=active)
        update_storage_scope = storage_scopes[0]
        return {
            'scope_id':
            update_storage_scope.identifier,
            'scope_key':
            update_storage_scope.scope_key,
            'fetcher':
            update_storage_scope.fetcher,
            'collector':
            update_storage_scope.collector,
            'state':
            update_storage_scope.state.isoformat(),
            'last_processed_timestamp':
            update_storage_scope.last_processed_timestamp.isoformat(),
            'active':
            update_storage_scope.active,
            'scope_activation_toggle_date':
            update_storage_scope.scope_activation_toggle_date.isoformat()
        }
コード例 #20
0
ファイル: collector.py プロジェクト: yashodhank/cloudkitty
    def get_all(self, collector=None):
        """Return the list of every services mapped to a collector.

        :param collector: Filter on the collector name.
        :return: Service to collector mappings collection.
        """
        policy.authorize(pecan.request.context, 'collector:list_mappings', {})
        mappings = [
            collector_models.ServiceToCollectorMapping(**mapping.as_dict())
            for mapping in self._db.list_mappings(collector)
        ]
        return collector_models.ServiceToCollectorMappingCollection(
            mappings=mappings)
コード例 #21
0
    def get_one(self, service):
        """Return a service to collector mapping.

        :param service: Name of the service to filter on.
        """
        LOG.warning("Collector mappings are deprecated and shouldn't be used.")
        policy.authorize(pecan.request.context, 'collector:get_mapping', {})
        try:
            mapping = self._db.get_mapping(service)
            return collector_models.ServiceToCollectorMapping(
                **mapping.as_dict())
        except db_api.NoSuchMapping as e:
            pecan.abort(404, e.args[0])
コード例 #22
0
    def tenants(self, begin=None, end=None):
        """Return the list of rated tenants.

        """
        policy.authorize(pecan.request.context, 'report:list_tenants', {})

        if not begin:
            begin = ck_utils.get_month_start()
        if not end:
            end = ck_utils.get_next_month()

        storage = pecan.request.storage_backend
        tenants = storage.get_tenants(begin, end)
        return tenants
コード例 #23
0
ファイル: dataframes.py プロジェクト: musaqib/cloudkitty
    def post(self, dataframes=[]):
        policy.authorize(
            flask.request.context,
            'dataframes:add',
            {},
        )

        if not dataframes:
            raise http_exceptions.BadRequest(
                "Parameter dataframes must not be empty.")

        self._storage.push(dataframes)

        return {}, 204
コード例 #24
0
ファイル: storage.py プロジェクト: yashodhank/cloudkitty
    def get_all(self, begin=None, end=None, tenant_id=None,
                resource_type=None):
        """Return a list of rated resources for a time period and a tenant.

        :param begin: Start of the period
        :param end: End of the period
        :param tenant_id: UUID of the tenant to filter on.
        :param resource_type: Type of the resource to filter on.
        :return: Collection of DataFrame objects.
        """

        policy.authorize(pecan.request.context, 'storage:list_data_frames', {})

        if not begin:
            begin = ck_utils.get_month_start()
        if not end:
            end = ck_utils.get_next_month()

        begin_ts = ck_utils.dt2ts(begin)
        end_ts = ck_utils.dt2ts(end)
        backend = pecan.request.storage_backend
        dataframes = []
        try:
            frames = backend.get_time_frame(begin_ts,
                                            end_ts,
                                            tenant_id=tenant_id,
                                            res_type=resource_type)
            for frame in frames:
                for service, data_list in frame['usage'].items():
                    frame_tenant = None
                    resources = []
                    for data in data_list:
                        desc = data['desc'] if data['desc'] else {}
                        price = decimal.Decimal(str(data['rating']['price']))
                        resource = storage_models.RatedResource(
                            service=service,
                            desc=desc,
                            volume=data['vol']['qty'],
                            rating=price)
                        frame_tenant = data['tenant_id']
                        resources.append(resource)
                    dataframe = storage_models.DataFrame(
                        begin=ck_utils.iso2dt(frame['period']['begin']),
                        end=ck_utils.iso2dt(frame['period']['end']),
                        tenant_id=frame_tenant,
                        resources=resources)
                    dataframes.append(dataframe)
        except ck_storage.NoTimeFrame:
            pass
        return storage_models.DataFrameCollection(dataframes=dataframes)
コード例 #25
0
    def summary(self,
                begin=None,
                end=None,
                tenant_id=None,
                service=None,
                groupby=None,
                all_tenants=False):
        """Return the summary to pay for a given period.

        """
        if not begin:
            begin = ck_utils.get_month_start()
        if not end:
            end = ck_utils.get_next_month()

        if all_tenants:
            tenant_id = None
        else:
            tenant_context = pecan.request.context.project_id
            tenant_id = tenant_context if not tenant_id else tenant_id
        policy.authorize(pecan.request.context, 'report:get_summary',
                         {"tenant_id": tenant_id})
        storage = pecan.request.storage_backend

        scope_key = CONF.collect.scope_key
        storage_groupby = []
        if groupby is not None and 'tenant_id' in groupby:
            storage_groupby.append(scope_key)
        if groupby is not None and 'res_type' in groupby:
            storage_groupby.append('type')
        filters = {scope_key: tenant_id} if tenant_id else None
        result = storage.total(groupby=storage_groupby,
                               begin=begin,
                               end=end,
                               metric_types=service,
                               filters=filters)

        summarymodels = []
        for res in result['results']:
            kwargs = {
                'res_type': res.get('type') or res.get('res_type'),
                'tenant_id': res.get(scope_key) or res.get('tenant_id'),
                'begin': res['begin'],
                'end': res['end'],
                'rate': res['rate'],
            }
            summarymodel = report_models.SummaryModel(**kwargs)
            summarymodels.append(summarymodel)

        return report_models.SummaryCollectionModel(summary=summarymodels)
コード例 #26
0
    def get(self,
            response_format=TABLE_RESPONSE_FORMAT,
            custom_fields=None,
            groupby=None,
            filters={},
            begin=None,
            end=None,
            offset=0,
            limit=100):

        if response_format not in ALL_RESPONSE_FORMATS:
            raise voluptuous.Invalid("Invalid response format [%s]. Valid "
                                     "format are [%s]." %
                                     (response_format, ALL_RESPONSE_FORMATS))

        policy.authorize(flask.request.context, 'summary:get_summary',
                         {'project_id': flask.request.context.project_id})
        begin = begin or tzutils.get_month_start()
        end = end or tzutils.get_next_month()

        if not flask.request.context.is_admin:
            if flask.request.context.project_id is None:
                # Unscoped non-admin user
                return {
                    'total': 0,
                    'columns': [],
                    'results': [],
                }
            filters['project_id'] = flask.request.context.project_id

        metric_types = filters.pop('type', [])
        if not isinstance(metric_types, list):
            metric_types = [metric_types]

        arguments = {
            'begin': begin,
            'end': end,
            'groupby': groupby,
            'filters': filters,
            'metric_types': metric_types,
            'offset': offset,
            'limit': limit,
            'paginate': True
        }
        if custom_fields:
            arguments['custom_fields'] = custom_fields

        total = self._storage.total(**arguments)

        return self.generate_response(response_format, total)
コード例 #27
0
ファイル: modules.py プロジェクト: openstack/cloudkitty
 def get(self, module_id):
     policy.authorize(flask.request.context, 'v2_rating:get_module', {})
     try:
         module = self.rating_modules[module_id]
     except KeyError:
         raise http_exceptions.NotFound(
             "Module '{}' not found".format(module_id))
     infos = module.obj.module_info.copy()
     return {
         'module_id': module_id,
         'description': infos['description'],
         'enabled': infos['enabled'],
         'hot_config': infos['hot_config'],
         'priority': infos['priority'],
     }
コード例 #28
0
ファイル: info.py プロジェクト: simhaonline/cloudkitty
def get_one_metric(metric_name):
    try:
        metrics_conf = collector.validate_conf(
            ck_utils.load_conf(CONF.collect.metrics_conf))
    except (voluptuous.Invalid, voluptuous.MultipleInvalid):
        msg = 'Invalid endpoint: no metrics in current configuration.'
        pecan.abort(405, msg)

    policy.authorize(pecan.request.context, 'info:get_metric_info', {})
    metric = _find_metric(metric_name, metrics_conf)
    if not metric:
        pecan.abort(404, str(metric_name))
    info = metric.copy()
    info['metric_id'] = info['alt_name']
    return info_models.CloudkittyMetricInfo(**info)
コード例 #29
0
    def get_one(self, module_id):
        """return a module

        :return: CloudKittyModule
        """
        policy.authorize(pecan.request.context, 'rating:get_module', {})

        try:
            lock = lockutils.lock('rating-modules')
            with lock:
                module = self.extensions[module_id]
        except KeyError:
            pecan.abort(404, 'Module not found.')
        infos = module.obj.module_info.copy()
        infos['module_id'] = infos.pop('name')
        return rating_models.CloudkittyModule(**infos)
コード例 #30
0
ファイル: info.py プロジェクト: simhaonline/cloudkitty
def get_all_metrics():
    try:
        metrics_conf = collector.validate_conf(
            ck_utils.load_conf(CONF.collect.metrics_conf))
    except (voluptuous.Invalid, voluptuous.MultipleInvalid):
        msg = 'Invalid endpoint: no metrics in current configuration.'
        pecan.abort(405, msg)

    policy.authorize(pecan.request.context, 'info:list_metrics_info', {})
    metrics_info_list = []
    for metric_name, metric in metrics_conf.items():
        info = metric.copy()
        info['metric_id'] = info['alt_name']
        metrics_info_list.append(info_models.CloudkittyMetricInfo(**info))
    return info_models.CloudkittyMetricInfoCollection(
        metrics=metrics_info_list)