Added Monasca Helper

In this changeset, I implemented a Helper class to deal with
Monasca requests.

Change-Id: I14cfab2c45451b8bb2ea5f1f48254b41fa5abae8
Partially-Implements: blueprint monasca-support
This commit is contained in:
Vincent Françoise 2016-07-29 15:50:25 +02:00
parent dad90b63fd
commit a015af1bd2
20 changed files with 411 additions and 198 deletions

View File

@ -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-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0
python-glanceclient>=2.5.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0
python-keystoneclient>=3.8.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-neutronclient>=5.1.0 # Apache-2.0
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
python-openstackclient>=3.3.0 # Apache-2.0 python-openstackclient>=3.3.0 # Apache-2.0

View File

@ -15,9 +15,9 @@ from cinderclient import client as ciclient
from glanceclient import client as glclient from glanceclient import client as glclient
from keystoneauth1 import loading as ka_loading from keystoneauth1 import loading as ka_loading
from keystoneclient import client as keyclient from keystoneclient import client as keyclient
from monascaclient import client as monclient
from neutronclient.neutron import client as netclient from neutronclient.neutron import client as netclient
from novaclient import client as nvclient from novaclient import client as nvclient
from oslo_config import cfg
from watcher.common import exception from watcher.common import exception
@ -41,12 +41,13 @@ class OpenStackClients(object):
self._glance = None self._glance = None
self._cinder = None self._cinder = None
self._ceilometer = None self._ceilometer = None
self._monasca = None
self._neutron = None self._neutron = None
def _get_keystone_session(self): 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) _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, _CLIENTS_AUTH_GROUP,
auth=auth) auth=auth)
return sess return sess
@ -62,7 +63,7 @@ class OpenStackClients(object):
return self._session return self._session
def _get_client_option(self, client, option): 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 @exception.wrap_keystone_exception
def keystone(self): def keystone(self):
@ -112,6 +113,35 @@ class OpenStackClients(object):
session=self.session) session=self.session)
return self._ceilometer 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 @exception.wrap_keystone_exception
def neutron(self): def neutron(self):
if self._neutron: if self._neutron:

View File

@ -28,6 +28,7 @@ from watcher.conf import db
from watcher.conf import decision_engine from watcher.conf import decision_engine
from watcher.conf import exception from watcher.conf import exception
from watcher.conf import glance_client from watcher.conf import glance_client
from watcher.conf import monasca_client
from watcher.conf import neutron_client from watcher.conf import neutron_client
from watcher.conf import nova_client from watcher.conf import nova_client
from watcher.conf import paths from watcher.conf import paths
@ -46,6 +47,7 @@ db.register_opts(CONF)
planner.register_opts(CONF) planner.register_opts(CONF)
applier.register_opts(CONF) applier.register_opts(CONF)
decision_engine.register_opts(CONF) decision_engine.register_opts(CONF)
monasca_client.register_opts(CONF)
nova_client.register_opts(CONF) nova_client.register_opts(CONF)
glance_client.register_opts(CONF) glance_client.register_opts(CONF)
cinder_client.register_opts(CONF) cinder_client.register_opts(CONF)

View File

@ -0,0 +1,36 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2016 Intel Corp
#
# Authors: Prudhvi Rao Shedimbi <prudhvi.rao.shedimbi@intel.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.
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)]

View File

@ -165,9 +165,9 @@ class CeilometerHelper(object):
values = [] values = []
for index, sample in enumerate(samples): for index, sample in enumerate(samples):
values.append( values.append(
{'sample_%s' % index: {'timestamp': sample._info['timestamp'], {'sample_%s' % index: {
'value': sample._info[ 'timestamp': sample._info['timestamp'],
'counter_volume']}}) 'value': sample._info['counter_volume']}})
return values return values
def get_last_sample_value(self, resource_id, meter_name): def get_last_sample_value(self, resource_id, meter_name):

View File

@ -0,0 +1,124 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2016 b<>com
#
# Authors: Vincent FRANCOISE <vincent.francoise@b-com.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 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

View File

@ -1,80 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.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.
#
"""
The :ref:`Cluster History <cluster_history_definition>` contains all the
previously collected timestamped data such as metrics and events associated
to any :ref:`managed resource <managed_resource_definition>` of the
:ref:`Cluster <cluster_definition>`.
Just like the :ref:`Cluster Data Model <cluster_data_model_definition>`, this
history may be used by any :ref:`Strategy <strategy_definition>` in order to
find the most optimal :ref:`Solution <solution_definition>` during an
:ref:`Audit <audit_definition>`.
In the Watcher project, a generic
:ref:`Cluster History <cluster_history_definition>`
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 <http://docs.openstack.org/admin-guide/telemetry-measurements.html>`_
- share common meter types (Cumulative, Delta, Gauge) based on what is
defined in Ceilometer.
See `the full list of meter types <http://docs.openstack.org/admin-guide/telemetry-measurements.html>`_
- simplify the development of a new :ref:`Strategy <strategy_definition>`
- avoid duplicating the same code in several
:ref:`Strategies <strategy_definition>`
- have a better consistency between the different
:ref:`Strategies <strategy_definition>`
- 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 <strategy_definition>` is able to produce a
:ref:`Solution <solution_definition>` for the requested
:ref:`Goal <goal_definition>`.
The :ref:`Cluster History <cluster_history_definition>` 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()

View File

@ -1,45 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.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.
#
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)

View File

@ -39,7 +39,7 @@ from oslo_log import log
from watcher._i18n import _, _LE, _LI, _LW from watcher._i18n import _, _LE, _LI, _LW
from watcher.common import exception 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.model import element
from watcher.decision_engine.strategy.strategies import base from watcher.decision_engine.strategy.strategies import base
@ -114,7 +114,7 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
@property @property
def ceilometer(self): def ceilometer(self):
if self._ceilometer is None: if self._ceilometer is None:
self._ceilometer = cch.CeilometerClusterHistory(osc=self.osc) self._ceilometer = ceil.CeilometerHelper(osc=self.osc)
return self._ceilometer return self._ceilometer
@ceilometer.setter @ceilometer.setter

View File

@ -32,7 +32,7 @@ from oslo_log import log
from watcher._i18n import _, _LW, _LI from watcher._i18n import _, _LW, _LI
from watcher.common import exception as wexc 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.model import element
from watcher.decision_engine.strategy.strategies import base from watcher.decision_engine.strategy.strategies import base
@ -114,7 +114,7 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
@property @property
def ceilometer(self): def ceilometer(self):
if self._ceilometer is None: if self._ceilometer is None:
self._ceilometer = ceil.CeilometerClusterHistory(osc=self.osc) self._ceilometer = ceil.CeilometerHelper(osc=self.osc)
return self._ceilometer return self._ceilometer
@ceilometer.setter @ceilometer.setter

View File

@ -47,7 +47,7 @@ from oslo_log import log
from watcher._i18n import _, _LE, _LI, _LW from watcher._i18n import _, _LE, _LI, _LW
from watcher.common import exception as wexc 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.model import element
from watcher.decision_engine.strategy.strategies import base from watcher.decision_engine.strategy.strategies import base
@ -110,7 +110,7 @@ class UniformAirflow(base.BaseStrategy):
@property @property
def ceilometer(self): def ceilometer(self):
if self._ceilometer is None: if self._ceilometer is None:
self._ceilometer = ceil.CeilometerClusterHistory(osc=self.osc) self._ceilometer = ceil.CeilometerHelper(osc=self.osc)
return self._ceilometer return self._ceilometer
@ceilometer.setter @ceilometer.setter

View File

@ -58,8 +58,7 @@ import six
from watcher._i18n import _, _LE, _LI from watcher._i18n import _, _LE, _LI
from watcher.common import exception from watcher.common import exception
from watcher.decision_engine.cluster.history import ceilometer \ from watcher.datasource import ceilometer as ceil
as ceilometer_cluster_history
from watcher.decision_engine.model import element from watcher.decision_engine.model import element
from watcher.decision_engine.strategy.strategies import base from watcher.decision_engine.strategy.strategies import base
@ -91,8 +90,7 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
@property @property
def ceilometer(self): def ceilometer(self):
if self._ceilometer is None: if self._ceilometer is None:
self._ceilometer = (ceilometer_cluster_history. self._ceilometer = ceil.CeilometerHelper(osc=self.osc)
CeilometerClusterHistory(osc=self.osc))
return self._ceilometer return self._ceilometer
@ceilometer.setter @ceilometer.setter

View File

@ -51,7 +51,7 @@ from oslo_log import log
from watcher._i18n import _, _LE, _LI, _LW from watcher._i18n import _, _LE, _LI, _LW
from watcher.common import exception as wexc 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.model import element
from watcher.decision_engine.strategy.strategies import base from watcher.decision_engine.strategy.strategies import base
@ -108,7 +108,7 @@ class WorkloadBalance(base.WorkloadStabilizationBaseStrategy):
@property @property
def ceilometer(self): def ceilometer(self):
if self._ceilometer is None: if self._ceilometer is None:
self._ceilometer = ceil.CeilometerClusterHistory(osc=self.osc) self._ceilometer = ceil.CeilometerHelper(osc=self.osc)
return self._ceilometer return self._ceilometer
@ceilometer.setter @ceilometer.setter

View File

@ -40,8 +40,7 @@ import oslo_utils
from watcher._i18n import _LI, _LW, _ from watcher._i18n import _LI, _LW, _
from watcher.common import exception from watcher.common import exception
from watcher.decision_engine.cluster.history import ceilometer as \ from watcher.datasource import ceilometer as ceil
ceilometer_cluster_history
from watcher.decision_engine.model import element from watcher.decision_engine.model import element
from watcher.decision_engine.strategy.strategies import base from watcher.decision_engine.strategy.strategies import base
@ -157,8 +156,7 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
@property @property
def ceilometer(self): def ceilometer(self):
if self._ceilometer is None: if self._ceilometer is None:
self._ceilometer = (ceilometer_cluster_history. self._ceilometer = ceil.CeilometerHelper(osc=self.osc)
CeilometerClusterHistory(osc=self.osc))
return self._ceilometer return self._ceilometer
@property @property

View File

@ -17,38 +17,31 @@ from cinderclient.v1 import client as ciclient_v1
from glanceclient import client as glclient from glanceclient import client as glclient
from keystoneauth1 import loading as ka_loading from keystoneauth1 import loading as ka_loading
import mock 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.neutron import client as netclient
from neutronclient.v2_0 import client as netclient_v2 from neutronclient.v2_0 import client as netclient_v2
from novaclient import client as nvclient from novaclient import client as nvclient
from oslo_config import cfg
from watcher.common import clients from watcher.common import clients
from watcher import conf
from watcher.tests import base from watcher.tests import base
CONF = conf.CONF
class TestClients(base.TestCase): class TestClients(base.TestCase):
def setUp(self): def _register_watcher_clients_auth_opts(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):
_AUTH_CONF_GROUP = 'watcher_clients_auth' _AUTH_CONF_GROUP = 'watcher_clients_auth'
ka_loading.register_auth_conf_options(cfg.CONF, _AUTH_CONF_GROUP) ka_loading.register_auth_conf_options(CONF, _AUTH_CONF_GROUP)
ka_loading.register_session_conf_options(cfg.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', # ka_loading.load_auth_from_conf_options(CONF, _AUTH_CONF_GROUP)
group=_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 # 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 # tests that run after this one will fail, complaining about required
@ -56,12 +49,18 @@ class TestClients(base.TestCase):
def cleanup_conf_from_loading(): def cleanup_conf_from_loading():
# oslo_config doesn't seem to allow unregistering groups through a # oslo_config doesn't seem to allow unregistering groups through a
# single method, so we do this instead # single method, so we do this instead
cfg.CONF.reset() CONF.reset()
del cfg.CONF._groups[_AUTH_CONF_GROUP] del CONF._groups[_AUTH_CONF_GROUP]
self.addCleanup(cleanup_conf_from_loading) 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', expected = {'username': 'foousername',
'password': 'foopassword', 'password': 'foopassword',
@ -69,14 +68,6 @@ class TestClients(base.TestCase):
'user_domain_id': 'foouserdomainid', 'user_domain_id': 'foouserdomainid',
'project_domain_id': 'fooprojdomainid'} '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 # Because some of the conf options for auth plugins are not registered
# until right before they are loaded, and because the method that does # until right before they are loaded, and because the method that does
# the actual loading of the conf option values is an anonymous method # 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) ret = original_register_opts(*args, **kwargs)
if 'group' in kwargs and kwargs['group'] == _AUTH_CONF_GROUP: if 'group' in kwargs and kwargs['group'] == _AUTH_CONF_GROUP:
for key, value in expected.items(): 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 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 sess = osc.session
self.assertEqual(expected['auth_url'], sess.auth.auth_url) self.assertEqual(expected['auth_url'], sess.auth.auth_url)
@ -107,13 +109,12 @@ class TestClients(base.TestCase):
osc = clients.OpenStackClients() osc = clients.OpenStackClients()
osc._nova = None osc._nova = None
osc.nova() 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) session=mock_session)
@mock.patch.object(clients.OpenStackClients, 'session') @mock.patch.object(clients.OpenStackClients, 'session')
def test_clients_nova_diff_vers(self, mock_session): def test_clients_nova_diff_vers(self, mock_session):
cfg.CONF.set_override('api_version', '2.3', CONF.set_override('api_version', '2.3', group='nova_client')
group='nova_client')
osc = clients.OpenStackClients() osc = clients.OpenStackClients()
osc._nova = None osc._nova = None
osc.nova() osc.nova()
@ -133,13 +134,12 @@ class TestClients(base.TestCase):
osc = clients.OpenStackClients() osc = clients.OpenStackClients()
osc._glance = None osc._glance = None
osc.glance() 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) session=mock_session)
@mock.patch.object(clients.OpenStackClients, 'session') @mock.patch.object(clients.OpenStackClients, 'session')
def test_clients_glance_diff_vers(self, mock_session): def test_clients_glance_diff_vers(self, mock_session):
cfg.CONF.set_override('api_version', '1', CONF.set_override('api_version', '1', group='glance_client')
group='glance_client')
osc = clients.OpenStackClients() osc = clients.OpenStackClients()
osc._glance = None osc._glance = None
osc.glance() osc.glance()
@ -159,13 +159,12 @@ class TestClients(base.TestCase):
osc = clients.OpenStackClients() osc = clients.OpenStackClients()
osc._cinder = None osc._cinder = None
osc.cinder() 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) session=mock_session)
@mock.patch.object(clients.OpenStackClients, 'session') @mock.patch.object(clients.OpenStackClients, 'session')
def test_clients_cinder_diff_vers(self, mock_session): def test_clients_cinder_diff_vers(self, mock_session):
cfg.CONF.set_override('api_version', '1', CONF.set_override('api_version', '1', group='cinder_client')
group='cinder_client')
osc = clients.OpenStackClients() osc = clients.OpenStackClients()
osc._cinder = None osc._cinder = None
osc.cinder() osc.cinder()
@ -186,7 +185,7 @@ class TestClients(base.TestCase):
osc._ceilometer = None osc._ceilometer = None
osc.ceilometer() osc.ceilometer()
mock_call.assert_called_once_with( mock_call.assert_called_once_with(
cfg.CONF.ceilometer_client.api_version, CONF.ceilometer_client.api_version,
None, None,
session=mock_session) session=mock_session)
@ -196,8 +195,8 @@ class TestClients(base.TestCase):
mock_session): mock_session):
'''ceilometerclient currently only has one version (v2)''' '''ceilometerclient currently only has one version (v2)'''
mock_get_alarm_client.return_value = [mock.Mock(), mock.Mock()] mock_get_alarm_client.return_value = [mock.Mock(), mock.Mock()]
cfg.CONF.set_override('api_version', '2', CONF.set_override('api_version', '2',
group='ceilometer_client') group='ceilometer_client')
osc = clients.OpenStackClients() osc = clients.OpenStackClients()
osc._ceilometer = None osc._ceilometer = None
osc.ceilometer() osc.ceilometer()
@ -221,14 +220,14 @@ class TestClients(base.TestCase):
osc = clients.OpenStackClients() osc = clients.OpenStackClients()
osc._neutron = None osc._neutron = None
osc.neutron() 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) session=mock_session)
@mock.patch.object(clients.OpenStackClients, 'session') @mock.patch.object(clients.OpenStackClients, 'session')
def test_clients_neutron_diff_vers(self, mock_session): def test_clients_neutron_diff_vers(self, mock_session):
'''neutronclient currently only has one version (v2)''' '''neutronclient currently only has one version (v2)'''
cfg.CONF.set_override('api_version', '2.0', CONF.set_override('api_version', '2.0',
group='neutron_client') group='neutron_client')
osc = clients.OpenStackClients() osc = clients.OpenStackClients()
osc._neutron = None osc._neutron = None
osc.neutron() osc.neutron()
@ -242,3 +241,51 @@ class TestClients(base.TestCase):
neutron = osc.neutron() neutron = osc.neutron()
neutron_cached = osc.neutron() neutron_cached = osc.neutron()
self.assertEqual(neutron, neutron_cached) 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)

View File

@ -31,7 +31,7 @@ class TestListOpts(base.TestCase):
'DEFAULT', 'api', 'database', 'watcher_decision_engine', 'DEFAULT', 'api', 'database', 'watcher_decision_engine',
'watcher_applier', 'watcher_planner', 'nova_client', 'watcher_applier', 'watcher_planner', 'nova_client',
'glance_client', 'cinder_client', 'ceilometer_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()) self.opt_sections = list(dict(opts.list_opts()).keys())
def test_run_list_opts(self): def test_run_list_opts(self):

View File

@ -19,8 +19,8 @@ from __future__ import unicode_literals
import mock import mock
from watcher.common import ceilometer_helper
from watcher.common import clients from watcher.common import clients
from watcher.datasource import ceilometer as ceilometer_helper
from watcher.tests import base from watcher.tests import base

View File

@ -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)