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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from poppy.manager.base import analytics
|
||||||
from poppy.manager.base import background_job
|
from poppy.manager.base import background_job
|
||||||
from poppy.manager.base import driver
|
from poppy.manager.base import driver
|
||||||
from poppy.manager.base import flavors
|
from poppy.manager.base import flavors
|
||||||
|
@ -23,6 +24,7 @@ from poppy.manager.base import ssl_certificate
|
||||||
|
|
||||||
Driver = driver.ManagerDriverBase
|
Driver = driver.ManagerDriverBase
|
||||||
|
|
||||||
|
AnalyticsController = analytics.AnalyticsController
|
||||||
BackgroundJobController = background_job.BackgroundJobControllerBase
|
BackgroundJobController = background_job.BackgroundJobControllerBase
|
||||||
FlavorsController = flavors.FlavorsControllerBase
|
FlavorsController = flavors.FlavorsControllerBase
|
||||||
ServicesController = services.ServicesControllerBase
|
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):
|
def notification(self):
|
||||||
return self._notification
|
return self._notification
|
||||||
|
|
||||||
|
@abc.abstractproperty
|
||||||
|
def analytics_controller(self):
|
||||||
|
"""Returns the driver's analytics controller
|
||||||
|
|
||||||
|
:raises NotImplementedError
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
@abc.abstractproperty
|
@abc.abstractproperty
|
||||||
def services_controller(self):
|
def services_controller(self):
|
||||||
"""Returns the driver's services controller
|
"""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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
from poppy.manager.default import analytics
|
||||||
from poppy.manager.default import background_job
|
from poppy.manager.default import background_job
|
||||||
from poppy.manager.default import flavors
|
from poppy.manager.default import flavors
|
||||||
from poppy.manager.default import health
|
from poppy.manager.default import health
|
||||||
|
@ -21,6 +22,7 @@ from poppy.manager.default import services
|
||||||
from poppy.manager.default import ssl_certificate
|
from poppy.manager.default import ssl_certificate
|
||||||
|
|
||||||
|
|
||||||
|
Analytics = analytics.AnalyticsController
|
||||||
BackgroundJob = background_job.BackgroundJobController
|
BackgroundJob = background_job.BackgroundJobController
|
||||||
Home = home.DefaultHomeController
|
Home = home.DefaultHomeController
|
||||||
Flavors = flavors.DefaultFlavorsController
|
Flavors = flavors.DefaultFlavorsController
|
||||||
|
|
|
@ -28,6 +28,10 @@ class DefaultManagerDriver(base.Driver):
|
||||||
super(DefaultManagerDriver, self).__init__(
|
super(DefaultManagerDriver, self).__init__(
|
||||||
conf, storage, providers, dns, distributed_task, notification)
|
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)
|
@decorators.lazy_property(write=True)
|
||||||
def services_controller(self):
|
def services_controller(self):
|
||||||
return controllers.Services(self)
|
return controllers.Services(self)
|
||||||
|
|
|
@ -25,13 +25,13 @@ from poppy.transport.pecan.controllers.v1 import ssl_certificates
|
||||||
|
|
||||||
|
|
||||||
# Hoist into package namespace
|
# 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
|
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
|
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})
|
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):
|
class ServicesController(base.Controller, hooks.HookController):
|
||||||
|
|
||||||
__hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()]
|
__hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()]
|
||||||
|
@ -111,6 +143,7 @@ class ServicesController(base.Controller, hooks.HookController):
|
||||||
# so added it in __init__ method.
|
# so added it in __init__ method.
|
||||||
# see more in: http://pecan.readthedocs.org/en/latest/rest.html
|
# see more in: http://pecan.readthedocs.org/en/latest/rest.html
|
||||||
self.__class__.assets = ServiceAssetsController(driver)
|
self.__class__.assets = ServiceAssetsController(driver)
|
||||||
|
self.__class__.analytics = ServicesAnalyticsController(driver)
|
||||||
|
|
||||||
@pecan.expose('json')
|
@pecan.expose('json')
|
||||||
def get_all(self):
|
def get_all(self):
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
@ -482,6 +483,59 @@ def is_valid_flavor_id(flavor_id):
|
||||||
pass
|
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):
|
def abort_with_message(error_info):
|
||||||
pecan.abort(400, detail=util.help_escape(
|
pecan.abort(400, detail=util.help_escape(
|
||||||
getattr(error_info, "message", "")),
|
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