Merge "Add Gnocchi datasource"

This commit is contained in:
Jenkins
2017-03-24 09:00:33 +00:00
committed by Gerrit Code Review
9 changed files with 254 additions and 3 deletions

View File

@@ -27,6 +27,7 @@ pbr>=2.0.0 # Apache-2.0
pecan!=1.0.2,!=1.0.3,!=1.0.4,!=1.2,>=1.0.0 # BSD
PrettyTable<0.8,>=0.7.1 # BSD
voluptuous>=0.8.9 # BSD License
gnocchiclient>=2.7.0 # Apache-2.0
python-ceilometerclient>=2.5.0 # Apache-2.0
python-cinderclient>=2.0.1 # Apache-2.0
python-glanceclient>=2.5.0 # Apache-2.0

View File

@@ -13,6 +13,7 @@
from ceilometerclient import client as ceclient
from cinderclient import client as ciclient
from glanceclient import client as glclient
from gnocchiclient import client as gnclient
from keystoneauth1 import loading as ka_loading
from keystoneclient import client as keyclient
from monascaclient import client as monclient
@@ -39,6 +40,7 @@ class OpenStackClients(object):
self._keystone = None
self._nova = None
self._glance = None
self._gnocchi = None
self._cinder = None
self._ceilometer = None
self._monasca = None
@@ -92,6 +94,17 @@ class OpenStackClients(object):
session=self.session)
return self._glance
@exception.wrap_keystone_exception
def gnocchi(self):
if self._gnocchi:
return self._gnocchi
gnocchiclient_version = self._get_client_option('gnocchi',
'api_version')
self._gnocchi = gnclient.Client(gnocchiclient_version,
session=self.session)
return self._gnocchi
@exception.wrap_keystone_exception
def cinder(self):
if self._cinder:

View File

@@ -130,7 +130,7 @@ class OperationNotPermitted(NotAuthorized):
msg_fmt = _("Operation not permitted")
class Invalid(WatcherException):
class Invalid(WatcherException, ValueError):
msg_fmt = _("Unacceptable parameters")
code = 400
@@ -149,6 +149,10 @@ class ResourceNotFound(ObjectNotFound):
code = 404
class InvalidParameter(Invalid):
msg_fmt = _("%(parameter)s has to be of type %(parameter_type)s")
class InvalidIdentity(Invalid):
msg_fmt = _("Expected a uuid or int but received %(identity)s")

View File

@@ -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 gnocchi_client
from watcher.conf import monasca_client
from watcher.conf import neutron_client
from watcher.conf import nova_client
@@ -50,6 +51,7 @@ decision_engine.register_opts(CONF)
monasca_client.register_opts(CONF)
nova_client.register_opts(CONF)
glance_client.register_opts(CONF)
gnocchi_client.register_opts(CONF)
cinder_client.register_opts(CONF)
ceilometer_client.register_opts(CONF)
neutron_client.register_opts(CONF)

View File

@@ -0,0 +1,42 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2017 Servionica
#
# Authors: Alexander Chadin <a.chadin@servionica.ru>
#
# 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
gnocchi_client = cfg.OptGroup(name='gnocchi_client',
title='Configuration Options for Gnocchi')
GNOCCHI_CLIENT_OPTS = [
cfg.StrOpt('api_version',
default='1',
help='Version of Gnocchi API to use in gnocchiclient.'),
cfg.IntOpt('query_max_retries',
default=10,
help='How many times Watcher is trying to query again'),
cfg.IntOpt('query_timeout',
default=1,
help='How many seconds Watcher should wait to do query again')]
def register_opts(conf):
conf.register_group(gnocchi_client)
conf.register_opts(GNOCCHI_CLIENT_OPTS, group=gnocchi_client)
def list_opts():
return [('gnocchi_client', GNOCCHI_CLIENT_OPTS)]

View File

@@ -0,0 +1,92 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2017 Servionica
#
# Authors: Alexander Chadin <a.chadin@servionica.ru>
#
# 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 datetime import datetime
import time
from oslo_config import cfg
from oslo_log import log
from watcher.common import clients
from watcher.common import exception
CONF = cfg.CONF
LOG = log.getLogger(__name__)
class GnocchiHelper(object):
def __init__(self, osc=None):
""":param osc: an OpenStackClients instance"""
self.osc = osc if osc else clients.OpenStackClients()
self.gnocchi = self.osc.gnocchi()
def query_retry(self, f, *args, **kwargs):
for i in range(CONF.gnocchi_client.query_max_retries):
try:
return f(*args, **kwargs)
except Exception as e:
LOG.exception(e)
time.sleep(CONF.gnocchi_client.query_timeout)
raise
def statistic_aggregation(self,
resource_id,
metric,
granularity,
start_time=None,
stop_time=None,
aggregation='mean'):
"""Representing a statistic aggregate by operators
:param metric: metric name of which we want the statistics
:param resource_id: id of resource to list statistics for
:param start_time: Start datetime from which metrics will be used
:param stop_time: End datetime from which metrics will be used
:param granularity: frequency of marking metric point, in seconds
:param aggregation: Should be chosen in accrodance with policy
aggregations
:return: value of aggregated metric
"""
if start_time is not None and not isinstance(start_time, datetime):
raise exception.InvalidParameter(parameter='start_time',
parameter_type=datetime)
if stop_time is not None and not isinstance(stop_time, datetime):
raise exception.InvalidParameter(parameter='stop_time',
parameter_type=datetime)
raw_kwargs = dict(
metric=metric,
start=start_time,
stop=stop_time,
resource_id=resource_id,
granularity=granularity,
aggregation=aggregation,
)
kwargs = {k: v for k, v in raw_kwargs.items() if k and v}
statistics = self.query_retry(
f=self.gnocchi.metric.get_measures, **kwargs)
if statistics:
# return value of latest measure
# measure has structure [time, granularity, value]
return statistics[-1][2]

View File

@@ -15,6 +15,8 @@ import ceilometerclient.v2.client as ceclient_v2
from cinderclient import client as ciclient
from cinderclient.v1 import client as ciclient_v1
from glanceclient import client as glclient
from gnocchiclient import client as gnclient
from gnocchiclient.v1 import client as gnclient_v1
from keystoneauth1 import loading as ka_loading
import mock
from monascaclient import client as monclient
@@ -157,6 +159,32 @@ class TestClients(base.TestCase):
glance_cached = osc.glance()
self.assertEqual(glance, glance_cached)
@mock.patch.object(gnclient, 'Client')
@mock.patch.object(clients.OpenStackClients, 'session')
def test_clients_gnocchi(self, mock_session, mock_call):
osc = clients.OpenStackClients()
osc._gnocchi = None
osc.gnocchi()
mock_call.assert_called_once_with(CONF.gnocchi_client.api_version,
session=mock_session)
@mock.patch.object(clients.OpenStackClients, 'session')
def test_clients_gnocchi_diff_vers(self, mock_session):
# gnocchiclient currently only has one version (v1)
CONF.set_override('api_version', '1', group='gnocchi_client')
osc = clients.OpenStackClients()
osc._gnocchi = None
osc.gnocchi()
self.assertEqual(gnclient_v1.Client, type(osc.gnocchi()))
@mock.patch.object(clients.OpenStackClients, 'session')
def test_clients_gnocchi_cached(self, mock_session):
osc = clients.OpenStackClients()
osc._gnocchi = None
gnocchi = osc.gnocchi()
gnocchi_cached = osc.gnocchi()
self.assertEqual(gnocchi, gnocchi_cached)
@mock.patch.object(ciclient, 'Client')
@mock.patch.object(clients.OpenStackClients, 'session')
def test_clients_cinder(self, mock_session, mock_call):

View File

@@ -30,8 +30,9 @@ class TestListOpts(base.TestCase):
self.base_sections = [
'DEFAULT', 'api', 'database', 'watcher_decision_engine',
'watcher_applier', 'watcher_planner', 'nova_client',
'glance_client', 'cinder_client', 'ceilometer_client',
'monasca_client', 'neutron_client', 'watcher_clients_auth']
'glance_client', 'gnocchi_client', 'cinder_client',
'ceilometer_client', 'monasca_client',
'neutron_client', 'watcher_clients_auth']
self.opt_sections = list(dict(opts.list_opts()).keys())
def test_run_list_opts(self):

View File

@@ -0,0 +1,68 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2017 Servionica
#
# 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 oslo_config import cfg
from oslo_utils import timeutils
from watcher.common import clients
from watcher.common import exception
from watcher.datasource import gnocchi as gnocchi_helper
from watcher.tests import base
CONF = cfg.CONF
@mock.patch.object(clients.OpenStackClients, 'gnocchi')
class TestGnocchiHelper(base.BaseTestCase):
def test_gnocchi_statistic_aggregation(self, mock_gnocchi):
gnocchi = mock.MagicMock()
expected_result = 5.5
expected_measures = [["2017-02-02T09:00:00.000000", 360, 5.5]]
gnocchi.metric.get_measures.return_value = expected_measures
mock_gnocchi.return_value = gnocchi
helper = gnocchi_helper.GnocchiHelper()
result = helper.statistic_aggregation(
resource_id='16a86790-327a-45f9-bc82-45839f062fdc',
metric='cpu_util',
granularity=360,
start_time=timeutils.parse_isotime("2017-02-02T09:00:00.000000"),
stop_time=timeutils.parse_isotime("2017-02-02T10:00:00.000000"),
aggregation='mean'
)
self.assertEqual(expected_result, result)
def test_gnocchi_wrong_datetime(self, mock_gnocchi):
gnocchi = mock.MagicMock()
expected_measures = [["2017-02-02T09:00:00.000000", 360, 5.5]]
gnocchi.metric.get_measures.return_value = expected_measures
mock_gnocchi.return_value = gnocchi
helper = gnocchi_helper.GnocchiHelper()
self.assertRaises(
exception.InvalidParameter, helper.statistic_aggregation,
resource_id='16a86790-327a-45f9-bc82-45839f062fdc',
metric='cpu_util',
granularity=360,
start_time="2017-02-02T09:00:00.000000",
stop_time=timeutils.parse_isotime("2017-02-02T10:00:00.000000"),
aggregation='mean')