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:
tonytan4ever 2015-10-30 14:39:20 -04:00
parent 29ee86f155
commit 6b0b65182e
11 changed files with 271 additions and 8 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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", "")),

View File

@ -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"
}
}

View File

@ -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)