diff --git a/.gitignore b/.gitignore index 1232bf9c..fb79c4c6 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,9 @@ nosetests.xml tests/cover tests/logs +# Hypothesis +.hypothesis/ + # Translations *.mo diff --git a/etc/poppy.conf b/etc/poppy.conf index ac3e9796..3579ccb9 100644 --- a/etc/poppy.conf +++ b/etc/poppy.conf @@ -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 diff --git a/poppy/bootstrap.py b/poppy/bootstrap.py index ca4f7781..2586f20e 100644 --- a/poppy/bootstrap.py +++ b/poppy/bootstrap.py @@ -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() diff --git a/poppy/manager/base/driver.py b/poppy/manager/base/driver.py index 23e97b75..a1757939 100644 --- a/poppy/manager/base/driver.py +++ b/poppy/manager/base/driver.py @@ -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 diff --git a/poppy/manager/default/analytics.py b/poppy/manager/default/analytics.py index d8a33a9e..1c592655 100644 --- a/poppy/manager/default/analytics.py +++ b/poppy/manager/default/analytics.py @@ -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) diff --git a/poppy/manager/default/driver.py b/poppy/manager/default/driver.py index e16bcb05..4e356bce 100644 --- a/poppy/manager/default/driver.py +++ b/poppy/manager/default/driver.py @@ -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): diff --git a/poppy/metrics/__init__.py b/poppy/metrics/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/poppy/metrics/base/__init__.py b/poppy/metrics/base/__init__.py new file mode 100644 index 00000000..2f7fd9d9 --- /dev/null +++ b/poppy/metrics/base/__init__.py @@ -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 diff --git a/poppy/metrics/base/controller.py b/poppy/metrics/base/controller.py new file mode 100644 index 00000000..6a435d02 --- /dev/null +++ b/poppy/metrics/base/controller.py @@ -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 diff --git a/poppy/metrics/base/driver.py b/poppy/metrics/base/driver.py new file mode 100644 index 00000000..54e59e0d --- /dev/null +++ b/poppy/metrics/base/driver.py @@ -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 diff --git a/poppy/metrics/base/services.py b/poppy/metrics/base/services.py new file mode 100644 index 00000000..cc47e834 --- /dev/null +++ b/poppy/metrics/base/services.py @@ -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 diff --git a/poppy/metrics/blueflood/__init__.py b/poppy/metrics/blueflood/__init__.py new file mode 100644 index 00000000..0cbf7c07 --- /dev/null +++ b/poppy/metrics/blueflood/__init__.py @@ -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 diff --git a/poppy/metrics/blueflood/controllers.py b/poppy/metrics/blueflood/controllers.py new file mode 100644 index 00000000..b223d498 --- /dev/null +++ b/poppy/metrics/blueflood/controllers.py @@ -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 diff --git a/poppy/metrics/blueflood/driver.py b/poppy/metrics/blueflood/driver.py new file mode 100644 index 00000000..4062ec9e --- /dev/null +++ b/poppy/metrics/blueflood/driver.py @@ -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) diff --git a/poppy/metrics/blueflood/services.py b/poppy/metrics/blueflood/services.py new file mode 100644 index 00000000..accb7577 --- /dev/null +++ b/poppy/metrics/blueflood/services.py @@ -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 diff --git a/poppy/metrics/blueflood/utils/__init__.py b/poppy/metrics/blueflood/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/setup.cfg b/setup.cfg index f4e6e969..facf519d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/tests/test-requirements.txt b/tests/test-requirements.txt index 3281ddf6..f44d0ffe 100644 --- a/tests/test-requirements.txt +++ b/tests/test-requirements.txt @@ -11,3 +11,4 @@ testrepository testtools python-heatclient beautifulsoup4 +hypothesis diff --git a/tests/unit/manager/default/test_flavors.py b/tests/unit/manager/default/test_flavors.py index 26469784..02754c66 100644 --- a/tests/unit/manager/default/test_flavors.py +++ b/tests/unit/manager/default/test_flavors.py @@ -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) diff --git a/tests/unit/manager/default/test_services.py b/tests/unit/manager/default/test_services.py index 0a53765c..8b10fd10 100644 --- a/tests/unit/manager/default/test_services.py +++ b/tests/unit/manager/default/test_services.py @@ -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) diff --git a/tests/unit/metrics/__init__.py b/tests/unit/metrics/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/metrics/blueflood/__init__.py b/tests/unit/metrics/blueflood/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/metrics/blueflood/test_driver.py b/tests/unit/metrics/blueflood/test_driver.py new file mode 100644 index 00000000..3339636a --- /dev/null +++ b/tests/unit/metrics/blueflood/test_driver.py @@ -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) diff --git a/tests/unit/metrics/blueflood/test_services.py b/tests/unit/metrics/blueflood/test_services.py new file mode 100644 index 00000000..5a8304df --- /dev/null +++ b/tests/unit/metrics/blueflood/test_services.py @@ -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 + )