Analytics Rest API code stub
- Add Restful pecan interface and validation logic into poppy. Partially Implements: blueprint analytics Change-Id: I85a05006d42fba9223ade191924447bb007ba677
This commit is contained in:
parent
29ee86f155
commit
6b0b65182e
|
@ -13,6 +13,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from poppy.manager.base import analytics
|
||||
from poppy.manager.base import background_job
|
||||
from poppy.manager.base import driver
|
||||
from poppy.manager.base import flavors
|
||||
|
@ -23,6 +24,7 @@ from poppy.manager.base import ssl_certificate
|
|||
|
||||
Driver = driver.ManagerDriverBase
|
||||
|
||||
AnalyticsController = analytics.AnalyticsController
|
||||
BackgroundJobController = background_job.BackgroundJobControllerBase
|
||||
FlavorsController = flavors.FlavorsControllerBase
|
||||
ServicesController = services.ServicesControllerBase
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# 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.manager.base import controller
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AnalyticsController(controller.ManagerControllerBase):
|
||||
"""Home controller base class."""
|
||||
|
||||
def __init__(self, manager):
|
||||
super(AnalyticsController, self).__init__(manager)
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_metrics_by_domain(self, project_id, domain_name, **extras):
|
||||
"""create_ssl_certificate
|
||||
|
||||
:param project_id
|
||||
:param domain_name
|
||||
:raises: NotImplementedError
|
||||
"""
|
||||
raise NotImplementedError
|
|
@ -66,6 +66,14 @@ class ManagerDriverBase(object):
|
|||
def notification(self):
|
||||
return self._notification
|
||||
|
||||
@abc.abstractproperty
|
||||
def analytics_controller(self):
|
||||
"""Returns the driver's analytics controller
|
||||
|
||||
:raises NotImplementedError
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abc.abstractproperty
|
||||
def services_controller(self):
|
||||
"""Returns the driver's services controller
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# 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.manager import base
|
||||
|
||||
|
||||
class AnalyticsController(base.AnalyticsController):
|
||||
|
||||
def get_metrics_by_domain(self, project_id, domain_name, **extras):
|
||||
return "Success"
|
|
@ -13,6 +13,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from poppy.manager.default import analytics
|
||||
from poppy.manager.default import background_job
|
||||
from poppy.manager.default import flavors
|
||||
from poppy.manager.default import health
|
||||
|
@ -21,6 +22,7 @@ from poppy.manager.default import services
|
|||
from poppy.manager.default import ssl_certificate
|
||||
|
||||
|
||||
Analytics = analytics.AnalyticsController
|
||||
BackgroundJob = background_job.BackgroundJobController
|
||||
Home = home.DefaultHomeController
|
||||
Flavors = flavors.DefaultFlavorsController
|
||||
|
|
|
@ -28,6 +28,10 @@ class DefaultManagerDriver(base.Driver):
|
|||
super(DefaultManagerDriver, self).__init__(
|
||||
conf, storage, providers, dns, distributed_task, notification)
|
||||
|
||||
@decorators.lazy_property(write=True)
|
||||
def analytics_controller(self):
|
||||
return controllers.Analytics(self)
|
||||
|
||||
@decorators.lazy_property(write=True)
|
||||
def services_controller(self):
|
||||
return controllers.Services(self)
|
||||
|
|
|
@ -25,13 +25,13 @@ from poppy.transport.pecan.controllers.v1 import ssl_certificates
|
|||
|
||||
|
||||
# Hoist into package namespace
|
||||
Home = home.HomeController
|
||||
Services = services.ServicesController
|
||||
Flavors = flavors.FlavorsController
|
||||
Ping = ping.PingController
|
||||
Health = health.HealthController
|
||||
DNSHealth = health.DNSHealthController
|
||||
StorageHealth = health.StorageHealthController
|
||||
ProviderHealth = health.ProviderHealthController
|
||||
Admin = admin.AdminController
|
||||
DNSHealth = health.DNSHealthController
|
||||
Flavors = flavors.FlavorsController
|
||||
Health = health.HealthController
|
||||
Home = home.HomeController
|
||||
Ping = ping.PingController
|
||||
ProviderHealth = health.ProviderHealthController
|
||||
Services = services.ServicesController
|
||||
SSLCertificate = ssl_certificates.SSLCertificateController
|
||||
StorageHealth = health.StorageHealthController
|
||||
|
|
|
@ -95,6 +95,38 @@ class ServiceAssetsController(base.Controller, hooks.HookController):
|
|||
return pecan.Response(None, 202, headers={"Location": service_url})
|
||||
|
||||
|
||||
class ServicesAnalyticsController(base.Controller, hooks.HookController):
|
||||
|
||||
__hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()]
|
||||
|
||||
@pecan.expose('json')
|
||||
@decorators.validate(
|
||||
service_id=rule.Rule(
|
||||
helpers.is_valid_service_id(),
|
||||
helpers.abort_with_message),
|
||||
request=rule.Rule(
|
||||
helpers.is_valid_analytics_request(),
|
||||
helpers.abort_with_message,
|
||||
stoplight_helpers.pecan_getter)
|
||||
)
|
||||
def get(self, service_id):
|
||||
'''Get Analytics By Domain Data'''
|
||||
|
||||
call_args = getattr(pecan.request.context,
|
||||
"call_args")
|
||||
domain = call_args.pop('domain')
|
||||
analytics_controller = \
|
||||
self._driver.manager.analytics_controller
|
||||
|
||||
res = analytics_controller.get_metrics_by_domain(
|
||||
self.project_id,
|
||||
domain,
|
||||
**call_args
|
||||
)
|
||||
|
||||
return pecan.Response(res, 200)
|
||||
|
||||
|
||||
class ServicesController(base.Controller, hooks.HookController):
|
||||
|
||||
__hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()]
|
||||
|
@ -111,6 +143,7 @@ class ServicesController(base.Controller, hooks.HookController):
|
|||
# so added it in __init__ method.
|
||||
# see more in: http://pecan.readthedocs.org/en/latest/rest.html
|
||||
self.__class__.assets = ServiceAssetsController(driver)
|
||||
self.__class__.analytics = ServicesAnalyticsController(driver)
|
||||
|
||||
@pecan.expose('json')
|
||||
def get_all(self):
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import functools
|
||||
import json
|
||||
import re
|
||||
|
@ -482,6 +483,59 @@ def is_valid_flavor_id(flavor_id):
|
|||
pass
|
||||
|
||||
|
||||
@decorators.validation_function
|
||||
def is_valid_analytics_request(request):
|
||||
default_end_time = datetime.datetime.utcnow()
|
||||
default_start_time = (datetime.datetime.utcnow()
|
||||
- datetime.timedelta(days=1))
|
||||
domain = request.GET.get('domain', "")
|
||||
startTime = request.GET.get('startTime',
|
||||
default_start_time.strftime(
|
||||
"%Y-%m-%dT%H:%M:%S"))
|
||||
endTime = request.GET.get('endTime',
|
||||
default_end_time.strftime("%Y-%m-%dT%H:%M:%S"))
|
||||
# Default metric type will be all metrics
|
||||
metricType = request.GET.get('metricType', None)
|
||||
|
||||
if not is_valid_domain_name(domain):
|
||||
raise exceptions.ValidationFailed("domain %s is not valid."
|
||||
% domain)
|
||||
|
||||
try:
|
||||
start_time = datetime.datetime.strptime(startTime,
|
||||
"%Y-%m-%dT%H:%M:%S")
|
||||
end_time = datetime.datetime.strptime(endTime,
|
||||
"%Y-%m-%dT%H:%M:%S")
|
||||
except Exception as e:
|
||||
raise exceptions.ValidationFailed('startTime or endTime is not in '
|
||||
'valid format. details: %s.'
|
||||
'Valid time stamp format is: '
|
||||
'YY-MM-DDTHH:MM:SS' % str(e))
|
||||
else:
|
||||
if start_time > end_time:
|
||||
raise exceptions.ValidationFailed('startTime cannot be later than'
|
||||
' endTime')
|
||||
|
||||
# Leave these 3 metric types for now.
|
||||
valid_metric_types = [
|
||||
'requestCount',
|
||||
'bandwithOut',
|
||||
'httpResponseCode'
|
||||
]
|
||||
if metricType not in valid_metric_types:
|
||||
raise exceptions.ValidationFailed('Must provide an metric name....'
|
||||
'Valid metric types are: %s' %
|
||||
valid_metric_types)
|
||||
|
||||
# Update context so the decorated function can get all this parameters
|
||||
request.context.call_args = {
|
||||
'domain': domain,
|
||||
'startTime': start_time,
|
||||
'endTime': end_time,
|
||||
'metricType': metricType
|
||||
}
|
||||
|
||||
|
||||
def abort_with_message(error_info):
|
||||
pecan.abort(400, detail=util.help_escape(
|
||||
getattr(error_info, "message", "")),
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"missing_domain": {
|
||||
},
|
||||
"missing_metric": {
|
||||
"domain": "www.abc.com"
|
||||
},
|
||||
"improper_datetime_format": {
|
||||
"domain": "www.abc.com",
|
||||
"metricType": "requestCount",
|
||||
"startTime": "November 14th, 2015 12:00",
|
||||
"endTime": "November 14th, 2015 14:00"
|
||||
},
|
||||
"start_later_than_end": {
|
||||
"domain": "www.abc.com",
|
||||
"metricType": "requestCount",
|
||||
"startTime": "2015-11-04T12:00",
|
||||
"endTime": "2015-11-04T10:00"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
# Copyright (c) 2015 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 datetime
|
||||
import uuid
|
||||
|
||||
import ddt
|
||||
import six
|
||||
|
||||
from tests.functional.transport.pecan import base
|
||||
|
||||
if six.PY2: # pragma: no cover
|
||||
import urllib # pragma: no cover
|
||||
else: # pragma: no cover
|
||||
import urllib.parse as urllib # pragma: no cover
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestServicesAnalytics(base.FunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestServicesAnalytics, self).setUp()
|
||||
|
||||
self.project_id = str(uuid.uuid1())
|
||||
self.service_id = str(uuid.uuid1())
|
||||
self.endTime = datetime.datetime.now()
|
||||
self.startTime = self.endTime - datetime.timedelta(hours=3)
|
||||
|
||||
def test_services_analytics_happy_path_with_default_timewindow(self):
|
||||
response = self.app.get('/v1.0/services/%s/analytics' %
|
||||
self.service_id,
|
||||
params=urllib.urlencode({
|
||||
'domain': 'abc.com',
|
||||
'metricType': 'requestCount',
|
||||
}),
|
||||
headers={
|
||||
'X-Project-ID': self.project_id
|
||||
})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_services_analytics_happy_path(self):
|
||||
response = self.app.get('/v1.0/services/%s/analytics' %
|
||||
self.service_id,
|
||||
params=urllib.urlencode({
|
||||
'domain': 'abc.com',
|
||||
'metricType': 'requestCount',
|
||||
'startTime': datetime.datetime.strftime(
|
||||
self.startTime, "%Y-%m-%dT%H:%M:%S"),
|
||||
'endTime': datetime.datetime.strftime(
|
||||
self.endTime, "%Y-%m-%dT%H:%M:%S")
|
||||
}),
|
||||
headers={
|
||||
'X-Project-ID': self.project_id
|
||||
})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@ddt.file_data("data_services_analytics_bad_input.json")
|
||||
def test_services_analytics_negative(self, get_params):
|
||||
response = self.app.get('/v1.0/services/%s/analytics' %
|
||||
self.service_id,
|
||||
params=urllib.urlencode(get_params),
|
||||
headers={
|
||||
'X-Project-ID': self.project_id
|
||||
},
|
||||
expect_errors=True)
|
||||
|
||||
self.assertEqual(response.status_code, 400)
|
Loading…
Reference in New Issue