diff --git a/requirements.txt b/requirements.txt index c017f4192..158988815 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,6 +31,7 @@ python-ceilometerclient>=2.5.0 # Apache-2.0 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 +python-monascaclient>=1.1.0 # Apache-2.0 python-neutronclient>=5.1.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-openstackclient>=3.3.0 # Apache-2.0 diff --git a/watcher/common/clients.py b/watcher/common/clients.py index f1227aa58..21f812402 100644 --- a/watcher/common/clients.py +++ b/watcher/common/clients.py @@ -15,9 +15,9 @@ from cinderclient import client as ciclient from glanceclient import client as glclient from keystoneauth1 import loading as ka_loading from keystoneclient import client as keyclient +from monascaclient import client as monclient from neutronclient.neutron import client as netclient from novaclient import client as nvclient -from oslo_config import cfg from watcher.common import exception @@ -41,12 +41,13 @@ class OpenStackClients(object): self._glance = None self._cinder = None self._ceilometer = None + self._monasca = None self._neutron = None def _get_keystone_session(self): - auth = ka_loading.load_auth_from_conf_options(cfg.CONF, + auth = ka_loading.load_auth_from_conf_options(CONF, _CLIENTS_AUTH_GROUP) - sess = ka_loading.load_session_from_conf_options(cfg.CONF, + sess = ka_loading.load_session_from_conf_options(CONF, _CLIENTS_AUTH_GROUP, auth=auth) return sess @@ -62,7 +63,7 @@ class OpenStackClients(object): return self._session def _get_client_option(self, client, option): - return getattr(getattr(cfg.CONF, '%s_client' % client), option) + return getattr(getattr(CONF, '%s_client' % client), option) @exception.wrap_keystone_exception def keystone(self): @@ -112,6 +113,35 @@ class OpenStackClients(object): session=self.session) return self._ceilometer + @exception.wrap_keystone_exception + def monasca(self): + if self._monasca: + return self._monasca + + monascaclient_version = self._get_client_option( + 'monasca', 'api_version') + token = self.session.get_token() + watcher_clients_auth_config = CONF.get(_CLIENTS_AUTH_GROUP) + service_type = 'monitoring' + monasca_kwargs = { + 'auth_url': watcher_clients_auth_config.auth_url, + 'cert_file': watcher_clients_auth_config.certfile, + 'insecure': watcher_clients_auth_config.insecure, + 'key_file': watcher_clients_auth_config.keyfile, + 'keystone_timeout': watcher_clients_auth_config.timeout, + 'os_cacert': watcher_clients_auth_config.cafile, + 'service_type': service_type, + 'token': token, + 'username': watcher_clients_auth_config.username, + 'password': watcher_clients_auth_config.password, + } + endpoint = self.session.get_endpoint(service_type=service_type) + + self._monasca = monclient.Client( + monascaclient_version, endpoint, **monasca_kwargs) + + return self._monasca + @exception.wrap_keystone_exception def neutron(self): if self._neutron: diff --git a/watcher/conf/__init__.py b/watcher/conf/__init__.py index e68cdbc19..ccf810aa3 100644 --- a/watcher/conf/__init__.py +++ b/watcher/conf/__init__.py @@ -28,6 +28,7 @@ from watcher.conf import db from watcher.conf import decision_engine from watcher.conf import exception from watcher.conf import glance_client +from watcher.conf import monasca_client from watcher.conf import neutron_client from watcher.conf import nova_client from watcher.conf import paths @@ -46,6 +47,7 @@ db.register_opts(CONF) planner.register_opts(CONF) applier.register_opts(CONF) decision_engine.register_opts(CONF) +monasca_client.register_opts(CONF) nova_client.register_opts(CONF) glance_client.register_opts(CONF) cinder_client.register_opts(CONF) diff --git a/watcher/conf/monasca_client.py b/watcher/conf/monasca_client.py new file mode 100644 index 000000000..72a279d17 --- /dev/null +++ b/watcher/conf/monasca_client.py @@ -0,0 +1,36 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 Intel Corp +# +# Authors: Prudhvi Rao Shedimbi +# +# 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 oslo_config import cfg + +monasca_client = cfg.OptGroup(name='monasca_client', + title='Configuration Options for Monasca') + +MONASCA_CLIENT_OPTS = [ + cfg.StrOpt('api_version', + default='2_0', + help='Version of Monasca API to use in monascaclient.')] + + +def register_opts(conf): + conf.register_group(monasca_client) + conf.register_opts(MONASCA_CLIENT_OPTS, group=monasca_client) + + +def list_opts(): + return [('monasca_client', MONASCA_CLIENT_OPTS)] diff --git a/watcher/decision_engine/cluster/__init__.py b/watcher/datasource/__init__.py similarity index 100% rename from watcher/decision_engine/cluster/__init__.py rename to watcher/datasource/__init__.py diff --git a/watcher/common/ceilometer_helper.py b/watcher/datasource/ceilometer.py similarity index 97% rename from watcher/common/ceilometer_helper.py rename to watcher/datasource/ceilometer.py index 43f5eeb5f..f0bf3e158 100644 --- a/watcher/common/ceilometer_helper.py +++ b/watcher/datasource/ceilometer.py @@ -165,9 +165,9 @@ class CeilometerHelper(object): values = [] for index, sample in enumerate(samples): values.append( - {'sample_%s' % index: {'timestamp': sample._info['timestamp'], - 'value': sample._info[ - 'counter_volume']}}) + {'sample_%s' % index: { + 'timestamp': sample._info['timestamp'], + 'value': sample._info['counter_volume']}}) return values def get_last_sample_value(self, resource_id, meter_name): diff --git a/watcher/datasource/monasca.py b/watcher/datasource/monasca.py new file mode 100644 index 000000000..a85d06fb0 --- /dev/null +++ b/watcher/datasource/monasca.py @@ -0,0 +1,124 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# +# Authors: Vincent FRANCOISE +# +# 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. + +import datetime + +from monascaclient import exc + +from watcher.common import clients + + +class MonascaHelper(object): + + def __init__(self, osc=None): + """:param osc: an OpenStackClients instance""" + self.osc = osc if osc else clients.OpenStackClients() + self.monasca = self.osc.monasca() + + def query_retry(self, f, *args, **kwargs): + try: + return f(*args, **kwargs) + except exc.HTTPUnauthorized: + self.osc.reset_clients() + self.monasca = self.osc.monasca() + return f(*args, **kwargs) + except Exception: + raise + + def _format_time_params(self, start_time, end_time, period): + """Format time-related params to the correct Monasca format + + :param start_time: Start datetime from which metrics will be used + :param end_time: End datetime from which metrics will be used + :param period: interval in seconds (int) + :return: start ISO time, end ISO time, period + """ + + if not period: + period = int(datetime.timedelta(hours=3).total_seconds()) + if not start_time: + start_time = ( + datetime.datetime.utcnow() - + datetime.timedelta(seconds=period)) + + start_timestamp = None if not start_time else start_time.isoformat() + end_timestamp = None if not end_time else end_time.isoformat() + + return start_timestamp, end_timestamp, period + + def statistics_list(self, meter_name, dimensions, start_time=None, + end_time=None, period=None,): + """List of statistics.""" + start_timestamp, end_timestamp, period = self._format_time_params( + start_time, end_time, period + ) + raw_kwargs = dict( + name=meter_name, + start_time=start_timestamp, + end_time=end_timestamp, + dimensions=dimensions, + ) + + kwargs = {k: v for k, v in raw_kwargs.items() if k and v} + + statistics = self.query_retry( + f=self.monasca.metrics.list_measurements, **kwargs) + + return statistics + + def statistic_aggregation(self, + meter_name, + dimensions, + start_time=None, + end_time=None, + period=None, + aggregate='avg', + group_by='*'): + """Representing a statistic aggregate by operators + + :param meter_name: meter names of which we want the statistics + :param dimensions: dimensions (dict) + :param start_time: Start datetime from which metrics will be used + :param end_time: End datetime from which metrics will be used + :param period: Sampling `period`: In seconds. If no period is given, + only one aggregate statistic is returned. If given, a + faceted result will be returned, divided into given + periods. Periods with no data are ignored. + :param aggregate: Should be either 'avg', 'count', 'min' or 'max' + :return: A list of dict with each dict being a distinct result row + """ + start_timestamp, end_timestamp, period = self._format_time_params( + start_time, end_time, period + ) + + raw_kwargs = dict( + name=meter_name, + start_time=start_timestamp, + end_time=end_timestamp, + dimensions=dimensions, + period=period, + statistics=aggregate, + group_by=group_by, + ) + + kwargs = {k: v for k, v in raw_kwargs.items() if k and v} + + statistics = self.query_retry( + f=self.monasca.metrics.list_statistics, **kwargs) + + return statistics diff --git a/watcher/decision_engine/cluster/history/base.py b/watcher/decision_engine/cluster/history/base.py deleted file mode 100644 index 89a7de17c..000000000 --- a/watcher/decision_engine/cluster/history/base.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- encoding: utf-8 -*- -# Copyright (c) 2015 b<>com -# -# Authors: Jean-Emile DARTOIS -# -# 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. -# - -""" -The :ref:`Cluster History ` contains all the -previously collected timestamped data such as metrics and events associated -to any :ref:`managed resource ` of the -:ref:`Cluster `. - -Just like the :ref:`Cluster Data Model `, this -history may be used by any :ref:`Strategy ` in order to -find the most optimal :ref:`Solution ` during an -:ref:`Audit `. - -In the Watcher project, a generic -:ref:`Cluster History ` -API is proposed with some helper classes in order to : - -- share a common measurement (events or metrics) naming based on what is - defined in Ceilometer. - See `the full list of available measurements `_ -- share common meter types (Cumulative, Delta, Gauge) based on what is - defined in Ceilometer. - See `the full list of meter types `_ -- simplify the development of a new :ref:`Strategy ` -- avoid duplicating the same code in several - :ref:`Strategies ` -- have a better consistency between the different - :ref:`Strategies ` -- avoid any strong coupling with any external metrics/events storage system - (the proposed API and measurement naming system acts as a pivot format) - -Note however that a developer can use his/her own history management system if -the Ceilometer system does not fit his/her needs as long as the -:ref:`Strategy ` is able to produce a -:ref:`Solution ` for the requested -:ref:`Goal `. - -The :ref:`Cluster History ` data may be persisted -in any appropriate storage system (InfluxDB, OpenTSDB, MongoDB,...). -""" # noqa - -import abc -import six - -"""Work in progress Helper to query metrics""" - - -@six.add_metaclass(abc.ABCMeta) -class BaseClusterHistory(object): - @abc.abstractmethod - def statistic_aggregation(self, resource_id, meter_name, period, - aggregate='avg'): - raise NotImplementedError() - - @abc.abstractmethod - def get_last_sample_values(self, resource_id, meter_name, limit=1): - raise NotImplementedError() - - def query_sample(self, meter_name, query, limit=1): - raise NotImplementedError() - - def statistic_list(self, meter_name, query=None, period=None): - raise NotImplementedError() diff --git a/watcher/decision_engine/cluster/history/ceilometer.py b/watcher/decision_engine/cluster/history/ceilometer.py deleted file mode 100644 index fef4bd186..000000000 --- a/watcher/decision_engine/cluster/history/ceilometer.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- encoding: utf-8 -*- -# Copyright (c) 2015 b<>com -# -# Authors: Jean-Emile DARTOIS -# -# 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 watcher.common import ceilometer_helper - -from watcher.decision_engine.cluster.history import base - - -class CeilometerClusterHistory(base.BaseClusterHistory): - def __init__(self, osc=None): - """:param osc: an OpenStackClients instance""" - super(CeilometerClusterHistory, self).__init__() - self.ceilometer = ceilometer_helper.CeilometerHelper(osc=osc) - - def statistic_list(self, meter_name, query=None, period=None): - return self.ceilometer.statistic_list(meter_name, query, period) - - def query_sample(self, meter_name, query, limit=1): - return self.ceilometer.query_sample(meter_name, query, limit) - - def get_last_sample_values(self, resource_id, meter_name, limit=1): - return self.ceilometer.get_last_sample_values(resource_id, meter_name, - limit) - - def statistic_aggregation(self, resource_id, meter_name, period, - aggregate='avg'): - return self.ceilometer.statistic_aggregation(resource_id, meter_name, - period, - aggregate) diff --git a/watcher/decision_engine/strategy/strategies/basic_consolidation.py b/watcher/decision_engine/strategy/strategies/basic_consolidation.py index 99df9b96a..90dfb917a 100644 --- a/watcher/decision_engine/strategy/strategies/basic_consolidation.py +++ b/watcher/decision_engine/strategy/strategies/basic_consolidation.py @@ -39,7 +39,7 @@ from oslo_log import log from watcher._i18n import _, _LE, _LI, _LW from watcher.common import exception -from watcher.decision_engine.cluster.history import ceilometer as cch +from watcher.datasource import ceilometer as ceil from watcher.decision_engine.model import element from watcher.decision_engine.strategy.strategies import base @@ -114,7 +114,7 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy): @property def ceilometer(self): if self._ceilometer is None: - self._ceilometer = cch.CeilometerClusterHistory(osc=self.osc) + self._ceilometer = ceil.CeilometerHelper(osc=self.osc) return self._ceilometer @ceilometer.setter diff --git a/watcher/decision_engine/strategy/strategies/outlet_temp_control.py b/watcher/decision_engine/strategy/strategies/outlet_temp_control.py index 539743445..e31aa51da 100644 --- a/watcher/decision_engine/strategy/strategies/outlet_temp_control.py +++ b/watcher/decision_engine/strategy/strategies/outlet_temp_control.py @@ -32,7 +32,7 @@ from oslo_log import log from watcher._i18n import _, _LW, _LI from watcher.common import exception as wexc -from watcher.decision_engine.cluster.history import ceilometer as ceil +from watcher.datasource import ceilometer as ceil from watcher.decision_engine.model import element from watcher.decision_engine.strategy.strategies import base @@ -114,7 +114,7 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy): @property def ceilometer(self): if self._ceilometer is None: - self._ceilometer = ceil.CeilometerClusterHistory(osc=self.osc) + self._ceilometer = ceil.CeilometerHelper(osc=self.osc) return self._ceilometer @ceilometer.setter diff --git a/watcher/decision_engine/strategy/strategies/uniform_airflow.py b/watcher/decision_engine/strategy/strategies/uniform_airflow.py index 767b1d0cf..c501b15d1 100644 --- a/watcher/decision_engine/strategy/strategies/uniform_airflow.py +++ b/watcher/decision_engine/strategy/strategies/uniform_airflow.py @@ -47,7 +47,7 @@ from oslo_log import log from watcher._i18n import _, _LE, _LI, _LW from watcher.common import exception as wexc -from watcher.decision_engine.cluster.history import ceilometer as ceil +from watcher.datasource import ceilometer as ceil from watcher.decision_engine.model import element from watcher.decision_engine.strategy.strategies import base @@ -110,7 +110,7 @@ class UniformAirflow(base.BaseStrategy): @property def ceilometer(self): if self._ceilometer is None: - self._ceilometer = ceil.CeilometerClusterHistory(osc=self.osc) + self._ceilometer = ceil.CeilometerHelper(osc=self.osc) return self._ceilometer @ceilometer.setter diff --git a/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py b/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py index 82bb9f80a..c96ab9115 100644 --- a/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py +++ b/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py @@ -58,8 +58,7 @@ import six from watcher._i18n import _, _LE, _LI from watcher.common import exception -from watcher.decision_engine.cluster.history import ceilometer \ - as ceilometer_cluster_history +from watcher.datasource import ceilometer as ceil from watcher.decision_engine.model import element from watcher.decision_engine.strategy.strategies import base @@ -91,8 +90,7 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy): @property def ceilometer(self): if self._ceilometer is None: - self._ceilometer = (ceilometer_cluster_history. - CeilometerClusterHistory(osc=self.osc)) + self._ceilometer = ceil.CeilometerHelper(osc=self.osc) return self._ceilometer @ceilometer.setter diff --git a/watcher/decision_engine/strategy/strategies/workload_balance.py b/watcher/decision_engine/strategy/strategies/workload_balance.py index b54cd66e8..bba6e7f29 100644 --- a/watcher/decision_engine/strategy/strategies/workload_balance.py +++ b/watcher/decision_engine/strategy/strategies/workload_balance.py @@ -51,7 +51,7 @@ from oslo_log import log from watcher._i18n import _, _LE, _LI, _LW from watcher.common import exception as wexc -from watcher.decision_engine.cluster.history import ceilometer as ceil +from watcher.datasource import ceilometer as ceil from watcher.decision_engine.model import element from watcher.decision_engine.strategy.strategies import base @@ -108,7 +108,7 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy): @property def ceilometer(self): if self._ceilometer is None: - self._ceilometer = ceil.CeilometerClusterHistory(osc=self.osc) + self._ceilometer = ceil.CeilometerHelper(osc=self.osc) return self._ceilometer @ceilometer.setter diff --git a/watcher/decision_engine/strategy/strategies/workload_stabilization.py b/watcher/decision_engine/strategy/strategies/workload_stabilization.py index ada3f3d5d..ed20e2def 100644 --- a/watcher/decision_engine/strategy/strategies/workload_stabilization.py +++ b/watcher/decision_engine/strategy/strategies/workload_stabilization.py @@ -40,8 +40,7 @@ import oslo_utils from watcher._i18n import _LI, _LW, _ from watcher.common import exception -from watcher.decision_engine.cluster.history import ceilometer as \ - ceilometer_cluster_history +from watcher.datasource import ceilometer as ceil from watcher.decision_engine.model import element from watcher.decision_engine.strategy.strategies import base @@ -157,8 +156,7 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy): @property def ceilometer(self): if self._ceilometer is None: - self._ceilometer = (ceilometer_cluster_history. - CeilometerClusterHistory(osc=self.osc)) + self._ceilometer = ceil.CeilometerHelper(osc=self.osc) return self._ceilometer @property diff --git a/watcher/tests/common/test_clients.py b/watcher/tests/common/test_clients.py index 8316b3ee6..81a213f1c 100644 --- a/watcher/tests/common/test_clients.py +++ b/watcher/tests/common/test_clients.py @@ -17,38 +17,31 @@ from cinderclient.v1 import client as ciclient_v1 from glanceclient import client as glclient from keystoneauth1 import loading as ka_loading import mock +from monascaclient import client as monclient +from monascaclient.v2_0 import client as monclient_v2 from neutronclient.neutron import client as netclient from neutronclient.v2_0 import client as netclient_v2 from novaclient import client as nvclient -from oslo_config import cfg from watcher.common import clients +from watcher import conf from watcher.tests import base +CONF = conf.CONF + class TestClients(base.TestCase): - def setUp(self): - super(TestClients, self).setUp() - - cfg.CONF.import_opt('api_version', 'watcher.common.clients', - group='nova_client') - cfg.CONF.import_opt('api_version', 'watcher.common.clients', - group='glance_client') - cfg.CONF.import_opt('api_version', 'watcher.common.clients', - group='cinder_client') - cfg.CONF.import_opt('api_version', 'watcher.common.clients', - group='ceilometer_client') - cfg.CONF.import_opt('api_version', 'watcher.common.clients', - group='neutron_client') - - def test_get_keystone_session(self): + def _register_watcher_clients_auth_opts(self): _AUTH_CONF_GROUP = 'watcher_clients_auth' - ka_loading.register_auth_conf_options(cfg.CONF, _AUTH_CONF_GROUP) - ka_loading.register_session_conf_options(cfg.CONF, _AUTH_CONF_GROUP) + ka_loading.register_auth_conf_options(CONF, _AUTH_CONF_GROUP) + ka_loading.register_session_conf_options(CONF, _AUTH_CONF_GROUP) + CONF.set_override('auth_type', 'password', group=_AUTH_CONF_GROUP) - cfg.CONF.set_override('auth_type', 'password', - group=_AUTH_CONF_GROUP) + # ka_loading.load_auth_from_conf_options(CONF, _AUTH_CONF_GROUP) + # ka_loading.load_session_from_conf_options(CONF, _AUTH_CONF_GROUP) + # CONF.set_override( + # 'auth-url', 'http://server.ip:35357', group=_AUTH_CONF_GROUP) # If we don't clean up the _AUTH_CONF_GROUP conf options, then other # tests that run after this one will fail, complaining about required @@ -56,12 +49,18 @@ class TestClients(base.TestCase): def cleanup_conf_from_loading(): # oslo_config doesn't seem to allow unregistering groups through a # single method, so we do this instead - cfg.CONF.reset() - del cfg.CONF._groups[_AUTH_CONF_GROUP] + CONF.reset() + del CONF._groups[_AUTH_CONF_GROUP] self.addCleanup(cleanup_conf_from_loading) - osc = clients.OpenStackClients() + def reset_register_opts_mock(conf_obj, original_method): + conf_obj.register_opts = original_method + + original_register_opts = CONF.register_opts + self.addCleanup(reset_register_opts_mock, + CONF, + original_register_opts) expected = {'username': 'foousername', 'password': 'foopassword', @@ -69,14 +68,6 @@ class TestClients(base.TestCase): 'user_domain_id': 'foouserdomainid', 'project_domain_id': 'fooprojdomainid'} - def reset_register_opts_mock(conf_obj, original_method): - conf_obj.register_opts = original_method - - original_register_opts = cfg.CONF.register_opts - self.addCleanup(reset_register_opts_mock, - cfg.CONF, - original_register_opts) - # Because some of the conf options for auth plugins are not registered # until right before they are loaded, and because the method that does # the actual loading of the conf option values is an anonymous method @@ -88,10 +79,21 @@ class TestClients(base.TestCase): ret = original_register_opts(*args, **kwargs) if 'group' in kwargs and kwargs['group'] == _AUTH_CONF_GROUP: for key, value in expected.items(): - cfg.CONF.set_override(key, value, group=_AUTH_CONF_GROUP) + CONF.set_override(key, value, group=_AUTH_CONF_GROUP) return ret - cfg.CONF.register_opts = mock_register_opts + CONF.register_opts = mock_register_opts + + def test_get_keystone_session(self): + self._register_watcher_clients_auth_opts() + + osc = clients.OpenStackClients() + + expected = {'username': 'foousername', + 'password': 'foopassword', + 'auth_url': 'http://server.ip:35357', + 'user_domain_id': 'foouserdomainid', + 'project_domain_id': 'fooprojdomainid'} sess = osc.session self.assertEqual(expected['auth_url'], sess.auth.auth_url) @@ -107,13 +109,12 @@ class TestClients(base.TestCase): osc = clients.OpenStackClients() osc._nova = None osc.nova() - mock_call.assert_called_once_with(cfg.CONF.nova_client.api_version, + mock_call.assert_called_once_with(CONF.nova_client.api_version, session=mock_session) @mock.patch.object(clients.OpenStackClients, 'session') def test_clients_nova_diff_vers(self, mock_session): - cfg.CONF.set_override('api_version', '2.3', - group='nova_client') + CONF.set_override('api_version', '2.3', group='nova_client') osc = clients.OpenStackClients() osc._nova = None osc.nova() @@ -133,13 +134,12 @@ class TestClients(base.TestCase): osc = clients.OpenStackClients() osc._glance = None osc.glance() - mock_call.assert_called_once_with(cfg.CONF.glance_client.api_version, + mock_call.assert_called_once_with(CONF.glance_client.api_version, session=mock_session) @mock.patch.object(clients.OpenStackClients, 'session') def test_clients_glance_diff_vers(self, mock_session): - cfg.CONF.set_override('api_version', '1', - group='glance_client') + CONF.set_override('api_version', '1', group='glance_client') osc = clients.OpenStackClients() osc._glance = None osc.glance() @@ -159,13 +159,12 @@ class TestClients(base.TestCase): osc = clients.OpenStackClients() osc._cinder = None osc.cinder() - mock_call.assert_called_once_with(cfg.CONF.cinder_client.api_version, + mock_call.assert_called_once_with(CONF.cinder_client.api_version, session=mock_session) @mock.patch.object(clients.OpenStackClients, 'session') def test_clients_cinder_diff_vers(self, mock_session): - cfg.CONF.set_override('api_version', '1', - group='cinder_client') + CONF.set_override('api_version', '1', group='cinder_client') osc = clients.OpenStackClients() osc._cinder = None osc.cinder() @@ -186,7 +185,7 @@ class TestClients(base.TestCase): osc._ceilometer = None osc.ceilometer() mock_call.assert_called_once_with( - cfg.CONF.ceilometer_client.api_version, + CONF.ceilometer_client.api_version, None, session=mock_session) @@ -196,8 +195,8 @@ class TestClients(base.TestCase): mock_session): '''ceilometerclient currently only has one version (v2)''' mock_get_alarm_client.return_value = [mock.Mock(), mock.Mock()] - cfg.CONF.set_override('api_version', '2', - group='ceilometer_client') + CONF.set_override('api_version', '2', + group='ceilometer_client') osc = clients.OpenStackClients() osc._ceilometer = None osc.ceilometer() @@ -221,14 +220,14 @@ class TestClients(base.TestCase): osc = clients.OpenStackClients() osc._neutron = None osc.neutron() - mock_call.assert_called_once_with(cfg.CONF.neutron_client.api_version, + mock_call.assert_called_once_with(CONF.neutron_client.api_version, session=mock_session) @mock.patch.object(clients.OpenStackClients, 'session') def test_clients_neutron_diff_vers(self, mock_session): '''neutronclient currently only has one version (v2)''' - cfg.CONF.set_override('api_version', '2.0', - group='neutron_client') + CONF.set_override('api_version', '2.0', + group='neutron_client') osc = clients.OpenStackClients() osc._neutron = None osc.neutron() @@ -242,3 +241,51 @@ class TestClients(base.TestCase): neutron = osc.neutron() neutron_cached = osc.neutron() self.assertEqual(neutron, neutron_cached) + + @mock.patch.object(monclient, 'Client') + @mock.patch.object(ka_loading, 'load_session_from_conf_options') + def test_clients_monasca(self, mock_session, mock_call): + mock_session.return_value = mock.Mock( + get_endpoint=mock.Mock(return_value='test_endpoint'), + get_token=mock.Mock(return_value='test_token'),) + + self._register_watcher_clients_auth_opts() + + osc = clients.OpenStackClients() + osc._monasca = None + osc.monasca() + mock_call.assert_called_once_with( + CONF.monasca_client.api_version, + 'test_endpoint', + auth_url='http://server.ip:35357', cert_file=None, insecure=False, + key_file=None, keystone_timeout=None, os_cacert=None, + password='foopassword', service_type='monitoring', + token='test_token', username='foousername') + + @mock.patch.object(ka_loading, 'load_session_from_conf_options') + def test_clients_monasca_diff_vers(self, mock_session): + mock_session.return_value = mock.Mock( + get_endpoint=mock.Mock(return_value='test_endpoint'), + get_token=mock.Mock(return_value='test_token'),) + + self._register_watcher_clients_auth_opts() + + CONF.set_override('api_version', '2_0', group='monasca_client') + osc = clients.OpenStackClients() + osc._monasca = None + osc.monasca() + self.assertEqual(monclient_v2.Client, type(osc.monasca())) + + @mock.patch.object(ka_loading, 'load_session_from_conf_options') + def test_clients_monasca_cached(self, mock_session): + mock_session.return_value = mock.Mock( + get_endpoint=mock.Mock(return_value='test_endpoint'), + get_token=mock.Mock(return_value='test_token'),) + + self._register_watcher_clients_auth_opts() + + osc = clients.OpenStackClients() + osc._monasca = None + monasca = osc.monasca() + monasca_cached = osc.monasca() + self.assertEqual(monasca, monasca_cached) diff --git a/watcher/tests/conf/test_list_opts.py b/watcher/tests/conf/test_list_opts.py index 3bd54138e..4e15cea78 100644 --- a/watcher/tests/conf/test_list_opts.py +++ b/watcher/tests/conf/test_list_opts.py @@ -31,7 +31,7 @@ class TestListOpts(base.TestCase): 'DEFAULT', 'api', 'database', 'watcher_decision_engine', 'watcher_applier', 'watcher_planner', 'nova_client', 'glance_client', 'cinder_client', 'ceilometer_client', - 'neutron_client', 'watcher_clients_auth'] + 'monasca_client', 'neutron_client', 'watcher_clients_auth'] self.opt_sections = list(dict(opts.list_opts()).keys()) def test_run_list_opts(self): diff --git a/watcher/decision_engine/cluster/history/__init__.py b/watcher/tests/datasource/__init__.py similarity index 100% rename from watcher/decision_engine/cluster/history/__init__.py rename to watcher/tests/datasource/__init__.py diff --git a/watcher/tests/common/test_ceilometer_helper.py b/watcher/tests/datasource/test_ceilometer_helper.py similarity index 98% rename from watcher/tests/common/test_ceilometer_helper.py rename to watcher/tests/datasource/test_ceilometer_helper.py index ea57cc818..fd051817f 100644 --- a/watcher/tests/common/test_ceilometer_helper.py +++ b/watcher/tests/datasource/test_ceilometer_helper.py @@ -19,8 +19,8 @@ from __future__ import unicode_literals import mock -from watcher.common import ceilometer_helper from watcher.common import clients +from watcher.datasource import ceilometer as ceilometer_helper from watcher.tests import base diff --git a/watcher/tests/datasource/test_monasca_helper.py b/watcher/tests/datasource/test_monasca_helper.py new file mode 100644 index 000000000..5c49af641 --- /dev/null +++ b/watcher/tests/datasource/test_monasca_helper.py @@ -0,0 +1,102 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2015 b<>com +# +# 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. + +import mock +from monascaclient import exc +from oslo_config import cfg +from oslo_utils import timeutils + +from watcher.common import clients +from watcher.datasource import monasca as monasca_helper +from watcher.tests import base + +CONF = cfg.CONF + + +@mock.patch.object(clients.OpenStackClients, 'monasca') +class TestMonascaHelper(base.BaseTestCase): + + def test_monasca_statistic_aggregation(self, mock_monasca): + monasca = mock.MagicMock() + expected_result = [{ + 'columns': ['timestamp', 'avg'], + 'dimensions': { + 'hostname': 'rdev-indeedsrv001', + 'service': 'monasca'}, + 'id': '0', + 'name': 'cpu.percent', + 'statistics': [ + ['2016-07-29T12:45:00Z', 0.0], + ['2016-07-29T12:50:00Z', 0.9100000000000001], + ['2016-07-29T12:55:00Z', 0.9111111111111112]]}] + + monasca.metrics.list_statistics.return_value = expected_result + mock_monasca.return_value = monasca + + helper = monasca_helper.MonascaHelper() + result = helper.statistic_aggregation( + meter_name='cpu.percent', + dimensions={'hostname': 'NODE_UUID'}, + start_time=timeutils.parse_isotime("2016-06-06T10:33:22.063176"), + end_time=None, + period=7200, + aggregate='avg', + group_by='*', + ) + self.assertEqual(expected_result, result) + + def test_monasca_statistic_list(self, mock_monasca): + monasca = mock.MagicMock() + expected_result = [{ + 'columns': ['timestamp', 'value', 'value_meta'], + 'dimensions': { + 'hostname': 'rdev-indeedsrv001', + 'service': 'monasca'}, + 'id': '0', + 'measurements': [ + ['2016-07-29T12:54:06.000Z', 0.9, {}], + ['2016-07-29T12:54:36.000Z', 0.9, {}], + ['2016-07-29T12:55:06.000Z', 0.9, {}], + ['2016-07-29T12:55:36.000Z', 0.8, {}]], + 'name': 'cpu.percent'}] + + monasca.metrics.list_measurements.return_value = expected_result + mock_monasca.return_value = monasca + helper = monasca_helper.MonascaHelper() + val = helper.statistics_list(meter_name="cpu.percent", dimensions={}) + self.assertEqual(expected_result, val) + + def test_monasca_statistic_list_query_retry(self, mock_monasca): + monasca = mock.MagicMock() + expected_result = [{ + 'columns': ['timestamp', 'value', 'value_meta'], + 'dimensions': { + 'hostname': 'rdev-indeedsrv001', + 'service': 'monasca'}, + 'id': '0', + 'measurements': [ + ['2016-07-29T12:54:06.000Z', 0.9, {}], + ['2016-07-29T12:54:36.000Z', 0.9, {}], + ['2016-07-29T12:55:06.000Z', 0.9, {}], + ['2016-07-29T12:55:36.000Z', 0.8, {}]], + 'name': 'cpu.percent'}] + + monasca.metrics.list_measurements.side_effect = [ + exc.HTTPUnauthorized, expected_result] + mock_monasca.return_value = monasca + helper = monasca_helper.MonascaHelper() + val = helper.statistics_list(meter_name="cpu.percent", dimensions={}) + self.assertEqual(expected_result, val)