feat: Driver for Metrics

- Add stevedore capability to load different metrics drivers,
  once their corresponding classes are built into poppy.

Partially Implements: blueprint analytics

Change-Id: Icb25ddccd0582bb6db34827995bce0f9af2087f0
This commit is contained in:
Sriram Madapusi Vasudevan 2016-01-11 11:50:10 -05:00
parent 4843ac4fb4
commit 4cff19885b
24 changed files with 433 additions and 9 deletions

3
.gitignore vendored
View File

@ -32,6 +32,9 @@ nosetests.xml
tests/cover
tests/logs
# Hypothesis
.hypothesis/
# Translations
*.mo

View File

@ -55,6 +55,10 @@ dns = default
# distributed_task driver module (e.g. TaskFlow, Celery, OsloMessaging)
distributed_task = taskflow
# Metrics module (e.g blueflood, kafka, gnocchi)
metrics = blueflood
[drivers:transport:limits]
max_services_per_page = 20
@ -123,6 +127,9 @@ auth_endpoint = ""
timeout = 30
delay = 1
[driver:metrics:blueflood]
blueflood_url = https://global.metrics.api.rackspacecloud.com/v2.0/{project_id}/views/
[drivers:provider]
default_cache_ttl = 86400

View File

@ -57,6 +57,8 @@ _DRIVER_OPTIONS = [
help='DNS driver to use'),
cfg.StrOpt('distributed_task', default='taskflow',
help='distributed_task driver to use'),
cfg.StrOpt('metrics', default='blueflood',
help='metrics driver to use'),
]
_DRIVER_GROUP = 'drivers'
@ -182,7 +184,7 @@ class Bootstrap(object):
manager_name = self.driver_conf.manager
args = [self.conf, self.storage, self.provider, self.dns,
self.distributed_task, self.notification]
self.distributed_task, self.notification, self.metrics]
try:
mgr = driver.DriverManager(namespace=manager_type,
@ -244,5 +246,31 @@ class Bootstrap(object):
except RuntimeError as exc:
LOG.exception(exc)
@decorators.lazy_property(write=False)
def metrics(self):
"""metrics driver.
:returns metrics driver
"""
LOG.debug("loading metrics driver")
# create the driver manager to load the appropriate drivers
metrics_driver_type = 'poppy.metrics'
metrics_driver_name = self.driver_conf.metrics
args = [self.conf]
LOG.debug((u'Loading metrics driver: %s'),
metrics_driver_name)
try:
mgr = driver.DriverManager(namespace=metrics_driver_type,
name=metrics_driver_name,
invoke_on_load=True,
invoke_args=args)
return mgr.driver
except RuntimeError as exc:
LOG.exception(exc)
def run(self):
self.transport.listen()

View File

@ -22,13 +22,14 @@ import six
class ManagerDriverBase(object):
"""Base class for driver manager."""
def __init__(self, conf, storage, providers, dns, distributed_task,
notification):
notification, metrics):
self._conf = conf
self._storage = storage
self._providers = providers
self._dns = dns
self._distributed_task = distributed_task
self._notification = notification
self._metrics = metrics
@property
def conf(self):
@ -66,6 +67,10 @@ class ManagerDriverBase(object):
def notification(self):
return self._notification
@property
def metrics(self):
return self._metrics
@abc.abstractproperty
def analytics_controller(self):
"""Returns the driver's analytics controller

View File

@ -12,6 +12,7 @@
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from poppy.manager import base
@ -19,4 +20,25 @@ from poppy.manager import base
class AnalyticsController(base.AnalyticsController):
def get_metrics_by_domain(self, project_id, domain_name, **extras):
return "Success"
# TODO(TheSriram): Insert call to metrics driver
self.metrics_controller = self._driver.metrics.services_controller
# NOTE(TheSriram): Returning Stubbed return value
metrics_response = {
"domain": "example.com",
"StatusCodes_2XX": [
{
"US": {
"1453136297": 24,
"1453049897": 45
}
},
{
"EMEA": {
"1453136297": 123,
"1453049897": 11
}
}
]
}
return json.dumps(metrics_response)

View File

@ -24,9 +24,10 @@ class DefaultManagerDriver(base.Driver):
"""Default Manager Driver."""
def __init__(self, conf, storage, providers, dns, distributed_task,
notification):
notification, metrics):
super(DefaultManagerDriver, self).__init__(
conf, storage, providers, dns, distributed_task, notification)
conf, storage, providers, dns, distributed_task, notification,
metrics)
@decorators.lazy_property(write=True)
def analytics_controller(self):

View File

View File

@ -0,0 +1,21 @@
# Copyright (c) 2016 Rackspace, Inc.
#
# 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 poppy.metrics.base import driver
from poppy.metrics.base import services
Driver = driver.MetricsDriverBase
ServicesController = services.ServicesControllerBase

View File

@ -0,0 +1,31 @@
# Copyright (c) 2016 Rackspace, Inc.
#
# 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 abc
import six
@six.add_metaclass(abc.ABCMeta)
class MetricsControllerBase(object):
"""Top-level class for controllers.
:param driver: Instance of the driver
instantiating this controller.
"""
def __init__(self, driver):
self._driver = driver

View File

@ -0,0 +1,37 @@
# Copyright (c) 2016 Rackspace, Inc.
#
# 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 abc
import six
@six.add_metaclass(abc.ABCMeta)
class MetricsDriverBase(object):
"""Interface definition for Metrics driver.
:param conf: Configuration containing options for this driver.
:type conf: `oslo_config.ConfigOpts`
"""
def __init__(self, conf):
self._conf = conf
@property
def conf(self):
"""conf
:returns conf
"""
return self._conf

View File

@ -0,0 +1,36 @@
# Copyright (c) 2016 Rackspace, Inc.
#
# 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 abc
import six
from poppy.metrics.base import controller
@six.add_metaclass(abc.ABCMeta)
class ServicesControllerBase(controller.MetricsControllerBase):
"""Services Controller Base class."""
def __init__(self, driver):
super(ServicesControllerBase, self).__init__(driver)
def read(self, metric_name, from_timestamp, to_timestamp, resolution):
"""read metrics from cache.
:raises NotImplementedError
"""
raise NotImplementedError

View File

@ -0,0 +1,21 @@
# Copyright (c) 2016 Rackspace, Inc.
#
# 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.
"""Cloud Metrics Cache driver for CDN"""
from poppy.metrics.blueflood import driver
# Hoist classes into package namespace
Driver = driver.BlueFloodMetricsDriver

View File

@ -0,0 +1,18 @@
# Copyright (c) 2016 Rackspace, Inc.
#
# 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 poppy.metrics.blueflood import services
ServicesController = services.ServicesController

View File

@ -0,0 +1,57 @@
# Copyright (c) 2016 Rackspace, Inc.
#
# 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
from poppy.metrics import base
from poppy.metrics.blueflood import controllers
BLUEFLOOD_OPTIONS = [
cfg.StrOpt('blueflood_url',
default='https://www.metrics.com',
help='Metrics url for retrieving cached content'),
]
BLUEFLOOD_GROUP = 'drivers:metrics:blueflood'
class BlueFloodMetricsDriver(base.Driver):
"""Blue Flood Metrics Driver."""
def __init__(self, conf):
super(BlueFloodMetricsDriver, self).__init__(conf)
conf.register_opts(BLUEFLOOD_OPTIONS, group=BLUEFLOOD_GROUP)
self.metrics_conf = conf[BLUEFLOOD_GROUP]
def is_alive(self):
"""Health check for Blue Flood Metrics driver."""
return True
@property
def metrics_driver_name(self):
"""metrics driver name.
:returns 'BlueFlood'
"""
return 'BlueFlood'
@property
def services_controller(self):
"""services_controller.
:returns service controller
"""
return controllers.ServicesController(self)

View File

@ -0,0 +1,31 @@
# Copyright (c) 2016 Rackspace, Inc.
#
# 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 poppy.metrics import base
class ServicesController(base.ServicesController):
def __init__(self, driver):
super(ServicesController, self).__init__(driver)
self.driver = driver
def read(self, metric_name, from_timestamp, to_timestamp, resolution):
"""read metrics from metrics driver.
"""
pass

View File

@ -61,6 +61,9 @@ poppy.provider =
poppy.distributed_task =
taskflow = poppy.distributed_task.taskflow:Driver
poppy.metrics =
blueflood = poppy.metrics.blueflood:Driver
poppy.notification =
mailgun = poppy.notification.mailgun:Driver

View File

@ -11,3 +11,4 @@ testrepository
testtools
python-heatclient
beautifulsoup4
hypothesis

View File

@ -31,8 +31,9 @@ class DefaultManagerFlavorTests(base.TestCase):
@mock.patch('poppy.dns.base.driver.DNSDriverBase')
@mock.patch('poppy.distributed_task.base.driver.DistributedTaskDriverBase')
@mock.patch('poppy.notification.base.driver.NotificationDriverBase')
@mock.patch('poppy.metrics.base.driver.MetricsDriverBase')
def setUp(self, mock_driver, mock_provider, mock_dns,
mock_distributed_task, mock_notification):
mock_distributed_task, mock_notification, mock_metrics):
super(DefaultManagerFlavorTests, self).setUp()
# create mocked config and driver
@ -47,7 +48,8 @@ class DefaultManagerFlavorTests(base.TestCase):
mock_provider,
mock_dns,
mock_distributed_task,
mock_notification)
mock_notification,
mock_metrics)
# stubbed driver
self.fc = flavors.DefaultFlavorsController(manager_driver)

View File

@ -102,8 +102,9 @@ class DefaultManagerServiceTests(base.TestCase):
@mock.patch('poppy.dns.base.driver.DNSDriverBase')
@mock.patch('poppy.storage.base.driver.StorageDriverBase')
@mock.patch('poppy.distributed_task.base.driver.DistributedTaskDriverBase')
@mock.patch('poppy.metrics.base.driver.MetricsDriverBase')
def setUp(self, mock_distributed_task, mock_storage,
mock_dns, mock_notification, mock_bootstrap):
mock_dns, mock_notification, mock_bootstrap, mock_metrics):
# NOTE(TheSriram): the mock.patch decorator applies mocks
# in the reverse order of the arguments present
super(DefaultManagerServiceTests, self).setUp()
@ -166,7 +167,8 @@ class DefaultManagerServiceTests(base.TestCase):
mock_providers,
mock_dns,
mock_distributed_task,
mock_notification)
mock_notification,
mock_metrics)
# stubbed driver
self.sc = services.DefaultServicesController(manager_driver)

View File

View File

View File

@ -0,0 +1,44 @@
# Copyright (c) 2016 Rackspace, Inc.
#
# 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.
"""Unittests for cloud_metrics cache driver implementation."""
from oslo_config import cfg
from poppy.metrics.blueflood import driver
from tests.unit import base
class TestBlueFloodMetricsDriver(base.TestCase):
def setUp(self):
super(TestBlueFloodMetricsDriver, self).setUp()
self.conf = cfg.ConfigOpts()
self.metrics_driver = (
driver.BlueFloodMetricsDriver(self.conf))
def test_init(self):
self.assertTrue(self.metrics_driver is not None)
def test_vendor_name(self):
self.assertEqual('BlueFlood', self.metrics_driver.metrics_driver_name)
def test_is_alive(self):
self.assertEqual(True, self.metrics_driver.is_alive())
def test_service_contoller(self):
self.assertTrue(self.metrics_driver.services_controller
is not None)

View File

@ -0,0 +1,54 @@
# Copyright (c) 2016 Rackspace, Inc.
#
# 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.
"""Unittests for BlueFlood metrics service_controller."""
import mock
from oslo_config import cfg
from poppy.metrics.blueflood import driver
from tests.unit import base
from hypothesis import given
from hypothesis import strategies
class TestBlueFloodServiceController(base.TestCase):
def setUp(self):
super(TestBlueFloodServiceController, self).setUp()
self.conf = cfg.ConfigOpts()
self.metrics_driver = (
driver.BlueFloodMetricsDriver(self.conf))
@given(strategies.text(), strategies.integers(),
strategies.integers(), strategies.integers())
def test_read(self, metric_name, from_timestamp, to_timestamp, resolution):
self.metrics_driver.services_controller.__class__.read = \
mock.Mock(return_value='success')
self.metrics_driver.services_controller.read(
metric_name=metric_name,
from_timestamp=from_timestamp,
to_timestamp=to_timestamp,
resolution=resolution
)
self.metrics_driver.services_controller.read.assert_called_once_with(
metric_name=metric_name,
from_timestamp=from_timestamp,
to_timestamp=to_timestamp,
resolution=resolution
)