Add a Monasca fetcher

This adds a Monasca fetcher for CloudKitty.

Change-Id: I0bf5af21987878d1e078021b0a9fbd0cc0a37b9d
Story: 2006675
Task: 36949
This commit is contained in:
Quentin Anglade 2019-10-07 14:53:33 +02:00 committed by Rafael Weingärtner
parent cbcdacac61
commit 8962a9ab63
9 changed files with 211 additions and 45 deletions

View File

@ -14,8 +14,6 @@
# under the License. # under the License.
# #
from keystoneauth1 import loading as ks_loading from keystoneauth1 import loading as ks_loading
from keystoneclient.v3 import client as ks_client
from monascaclient import client as mclient
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from voluptuous import All from voluptuous import All
@ -25,10 +23,10 @@ from voluptuous import Required
from voluptuous import Schema from voluptuous import Schema
from cloudkitty import collector from cloudkitty import collector
from cloudkitty.common import monasca_client as mon_client_utils
from cloudkitty import dataframe from cloudkitty import dataframe
from cloudkitty import utils as ck_utils from cloudkitty import utils as ck_utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
MONASCA_API_VERSION = '2_0' MONASCA_API_VERSION = '2_0'
@ -69,10 +67,6 @@ MONASCA_EXTRA_SCHEMA = {
} }
class EndpointNotFound(Exception):
"""Exception raised if the Monasca endpoint is not found"""
class MonascaCollector(collector.BaseCollector): class MonascaCollector(collector.BaseCollector):
collector_name = 'monasca' collector_name = 'monasca'
@ -93,41 +87,8 @@ class MonascaCollector(collector.BaseCollector):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(MonascaCollector, self).__init__(**kwargs) super(MonascaCollector, self).__init__(**kwargs)
self._conn = mon_client_utils.get_monasca_client(
self.auth = ks_loading.load_auth_from_conf_options( CONF, COLLECTOR_MONASCA_OPTS)
CONF,
COLLECTOR_MONASCA_OPTS)
self.session = ks_loading.load_session_from_conf_options(
CONF,
COLLECTOR_MONASCA_OPTS,
auth=self.auth)
self.ks_client = ks_client.Client(
session=self.session,
interface=CONF.collector_monasca.interface,
)
self.mon_endpoint = self._get_monasca_endpoint()
if not self.mon_endpoint:
raise EndpointNotFound()
self._conn = mclient.Client(
api_version=MONASCA_API_VERSION,
session=self.session,
endpoint=self.mon_endpoint)
# NOTE(lukapeschke) This function should be removed as soon as the endpoint
# it no longer required by monascaclient
def _get_monasca_endpoint(self):
service_name = cfg.CONF.collector_monasca.monasca_service_name
endpoint_interface_type = cfg.CONF.collector_monasca.interface
service_list = self.ks_client.services.list(name=service_name)
if not service_list:
return None
mon_service = service_list[0]
endpoints = self.ks_client.endpoints.list(mon_service.id)
for endpoint in endpoints:
if endpoint.interface == endpoint_interface_type:
return endpoint.url
return None
def _get_metadata(self, metric_name, conf): def _get_metadata(self, metric_name, conf):
info = {} info = {}

View File

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from keystoneauth1 import loading as ks_loading
from keystoneclient.v3 import client as ks_client
from monascaclient import client as mclient
MONASCA_API_VERSION = '2_0'
class EndpointNotFound(Exception):
"""Exception raised if the Monasca endpoint is not found"""
# NOTE(lukapeschke) This function should be removed as soon as the endpoint
# it no longer required by monascaclient
def get_monasca_endpoint(cfg, keystone_client):
service_name = cfg.monasca_service_name
endpoint_interface_type = cfg.interface
service_list = keystone_client.services.list(name=service_name)
if not service_list:
return None
mon_service = service_list[0]
endpoints = keystone_client.endpoints.list(mon_service.id)
for endpoint in endpoints:
if endpoint.interface == endpoint_interface_type:
return endpoint.url
return None
def get_monasca_client(conf, conf_opts):
ks_auth = ks_loading.load_auth_from_conf_options(conf, conf_opts)
session = ks_loading.load_session_from_conf_options(
conf,
conf_opts,
auth=ks_auth)
keystone_client = ks_client.Client(
session=session,
interface=conf[conf_opts].interface)
mon_endpoint = get_monasca_endpoint(conf[conf_opts], keystone_client)
if not mon_endpoint:
raise EndpointNotFound()
return mclient.Client(
api_version=MONASCA_API_VERSION,
session=session,
endpoint=mon_endpoint)

View File

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
# Copyright 2019 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from keystoneauth1 import loading as ks_loading
from oslo_config import cfg
from cloudkitty.common import monasca_client as mon_client_utils
from cloudkitty import fetcher
MONASCA_API_VERSION = '2_0'
FETCHER_MONASCA_OPTS = 'fetcher_monasca'
fetcher_monasca_opts = [
cfg.StrOpt('dimension_name',
default='project_id',
help='Monasca dimension from which scope_ids should be'
' collected.'),
cfg.StrOpt('monasca_tenant_id',
default=None,
help='If specified, monasca client will use this ID instead of'
' the default one.'),
cfg.StrOpt(
'monasca_service_name',
default='monasca',
help='Name of the Monasca service (defaults to monasca)',
),
cfg.StrOpt(
'interface',
default='internal',
help='Endpoint URL type (defaults to internal).',
),
]
CONF = cfg.CONF
cfg.CONF.register_opts(fetcher_monasca_opts, FETCHER_MONASCA_OPTS)
ks_loading.register_auth_conf_options(CONF, FETCHER_MONASCA_OPTS)
ks_loading.register_session_conf_options(CONF, FETCHER_MONASCA_OPTS)
class MonascaFetcher(fetcher.BaseFetcher):
"""Monasca fetcher"""
name = 'monasca'
def __init__(self):
self._conn = mon_client_utils.get_monasca_client(CONF,
FETCHER_MONASCA_OPTS)
def get_tenants(self):
kwargs = {
"tenant_id": CONF.fetcher_monasca.monasca_tenant_id,
"dimension_name": CONF.fetcher_monasca.dimension_name,
}
if kwargs['tenant_id'] is None:
del kwargs['tenant_id']
values = self._conn.metrics.list_dimension_values(**kwargs)
return [v['dimension_value'] for v in values]

View File

@ -44,8 +44,8 @@ class MonascaCollectorTest(tests.TestCase):
} }
} }
with mock.patch( with mock.patch(
'cloudkitty.collector.monasca.' 'cloudkitty.common.monasca_client.'
'MonascaCollector._get_monasca_endpoint', 'get_monasca_endpoint',
return_value='http://noop'): return_value='http://noop'):
self.collector = mon_collector.MonascaCollector( self.collector = mon_collector.MonascaCollector(
period=3600, period=3600,

View File

@ -0,0 +1,48 @@
# Copyright 2019 Objectif Libre
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from unittest import mock
from cloudkitty.fetcher import monasca as mon_fetcher
from cloudkitty import tests
class MonascaFetcherTest(tests.TestCase):
def setUp(self):
super(MonascaFetcherTest, self).setUp()
self.conf.set_override('dimension_name', 'dimension_name_test',
'fetcher_monasca')
self.conf.set_override('monasca_tenant_id', 'this_is_definitely_a_uid',
'fetcher_monasca')
self.conf.set_override('monasca_service_name', 'monasca-api-test',
'fetcher_monasca')
self.conf.set_override('interface', 'interface-test',
'fetcher_monasca')
with mock.patch(
'cloudkitty.common.monasca_client.'
'get_monasca_endpoint',
return_value='http://noop'):
self.fetcher = mon_fetcher.MonascaFetcher()
def test_get_tenants(self):
with mock.patch.object(self.fetcher._conn.metrics,
'list_dimension_values') as m:
self.fetcher.get_tenants()
m.assert_called_once_with(
tenant_id='this_is_definitely_a_uid',
dimension_name='dimension_name_test',
)

View File

@ -63,7 +63,7 @@ configured in CloudKitty's configuration file.
Fetcher Fetcher
======= =======
Four fetchers are available in cloudkitty: Five fetchers are available in CloudKitty:
* The ``keystone`` fetcher retrieves a list of projects on which the * The ``keystone`` fetcher retrieves a list of projects on which the
cloudkitty user has the ``rating`` role from Keystone. cloudkitty user has the ``rating`` role from Keystone.
@ -73,6 +73,9 @@ Four fetchers are available in cloudkitty:
discover new projects from Gnocchi when it is used with OpenStack. It can be discover new projects from Gnocchi when it is used with OpenStack. It can be
used in an OpenStack context or with a standalone Gnocchi deployment. used in an OpenStack context or with a standalone Gnocchi deployment.
* The ``monasca`` fetcher retrieves from `Monasca`_ all values from a
configurable metric dimension (``project_id`` by default).
* The ``prometheus`` fetcher works in a similar way to the Gnocchi fetcher, * The ``prometheus`` fetcher works in a similar way to the Gnocchi fetcher,
which allows to discover scopes from `Prometheus`_. which allows to discover scopes from `Prometheus`_.

View File

@ -60,6 +60,20 @@ fetcher using regular Keystone authentication options as found here:
:doc:`configuration`. :doc:`configuration`.
Monasca
-------
Section ``fetcher_monasca``.
* ``dimension_name``: Monasca dimension from which scope_ids is collected.
* ``monasca_tenant_id``: If specified, monasca client will use this ID
instead of the default one.
* ``monasca_service_name``: Name of the Monasca service (defaults to monasca).
* ``interface``: Endpoint URL type (defaults to internal).
Prometheus Prometheus
---------- ----------

View File

@ -0,0 +1,7 @@
---
features:
- |
Adds a Monasca fetcher retrieving scopes from Monasca dimensions. See the
`fetcher documentation
<https://docs.openstack.org/cloudkitty/latest/admin/configuration/fetcher.html#monasca>`__
for more details.

View File

@ -56,6 +56,7 @@ cloudkitty.fetchers =
source = cloudkitty.fetcher.source:SourceFetcher source = cloudkitty.fetcher.source:SourceFetcher
gnocchi = cloudkitty.fetcher.gnocchi:GnocchiFetcher gnocchi = cloudkitty.fetcher.gnocchi:GnocchiFetcher
prometheus = cloudkitty.fetcher.prometheus:PrometheusFetcher prometheus = cloudkitty.fetcher.prometheus:PrometheusFetcher
monasca = cloudkitty.fetcher.monasca:MonascaFetcher
cloudkitty.rating.processors = cloudkitty.rating.processors =
noop = cloudkitty.rating.noop:Noop noop = cloudkitty.rating.noop:Noop