refstack/refstack/api/controllers/v1.py

262 lines
8.7 KiB
Python

# Copyright (c) 2015 Mirantis, Inc.
# All Rights Reserved.
#
# 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.
"""Version 1 of the API."""
import json
from oslo_config import cfg
from oslo_log import log
import pecan
from pecan import rest
import re
import requests
import requests_cache
from six.moves.urllib import parse
from refstack import db
from refstack.api import constants as const
from refstack.api import utils as api_utils
from refstack.api.controllers import auth
from refstack.api.controllers import user
from refstack.common import validators
LOG = log.getLogger(__name__)
CTRLS_OPTS = [
cfg.IntOpt('results_per_page',
default=20,
help='Number of results for one page'),
cfg.StrOpt('input_date_format',
default='%Y-%m-%d %H:%M:%S',
help='The format for %(start)s and %(end)s parameters' % {
'start': const.START_DATE,
'end': const.END_DATE
})
]
CONF = cfg.CONF
CONF.register_opts(CTRLS_OPTS, group='api')
# Cached requests will expire after 10 minutes.
requests_cache.install_cache(cache_name='github_cache',
backend='memory',
expire_after=600)
class BaseRestControllerWithValidation(rest.RestController):
"""Rest controller with validation.
Controller provides validation for POSTed data
exposed endpoints:
POST base_url/
GET base_url/<item uid>
GET base_url/schema
"""
__validator__ = None
def __init__(self): # pragma: no cover
"""Init."""
if self.__validator__:
self.validator = self.__validator__()
else:
raise ValueError("__validator__ is not defined")
def get_item(self, item_id): # pragma: no cover
"""Handler for getting item."""
raise NotImplementedError
def store_item(self, item_in_json): # pragma: no cover
"""Handler for storing item. Should return new item id."""
raise NotImplementedError
@pecan.expose('json')
def get_one(self, arg):
"""Return test results in JSON format.
:param arg: item ID in uuid4 format or action
"""
if self.validator.assert_id(arg):
return self.get_item(item_id=arg)
elif arg == 'schema':
return self.validator.schema
else:
pecan.abort(404)
@pecan.expose('json')
def post(self, ):
"""POST handler."""
self.validator.validate(pecan.request)
item = json.loads(pecan.request.body)
item_id = self.store_item(item)
pecan.response.status = 201
return item_id
class ResultsController(BaseRestControllerWithValidation):
"""/v1/results handler."""
__validator__ = validators.TestResultValidator
def get_item(self, item_id):
"""Handler for getting item."""
test_info = db.get_test(item_id)
if not test_info:
pecan.abort(404)
test_list = db.get_test_results(item_id)
test_name_list = [test_dict[0] for test_dict in test_list]
return {"cpid": test_info.cpid,
"created_at": test_info.created_at,
"duration_seconds": test_info.duration_seconds,
"results": test_name_list}
def store_item(self, item_in_json):
"""Handler for storing item. Should return new item id."""
item = item_in_json.copy()
if pecan.request.headers.get('X-Public-Key'):
if 'metadata' not in item:
item['metadata'] = {}
item['metadata']['public_key'] = \
pecan.request.headers.get('X-Public-Key')
test_id = db.store_results(item)
LOG.debug(item)
return {'test_id': test_id,
'url': parse.urljoin(CONF.ui_url,
CONF.api.test_results_url) % test_id}
@pecan.expose('json')
def get(self):
"""Get information of all uploaded test results.
Get information of all uploaded test results in descending
chronological order. Make it possible to specify some
input parameters for filtering.
For example:
/v1/results?page=<page number>&cpid=1234.
By default, page is set to page number 1,
if the page parameter is not specified.
"""
expected_input_params = [
const.START_DATE,
const.END_DATE,
const.CPID,
]
try:
filters = api_utils.parse_input_params(expected_input_params)
records_count = db.get_test_records_count(filters)
page_number, total_pages_number = \
api_utils.get_page_number(records_count)
except api_utils.ParseInputsError as ex:
pecan.abort(400, 'Reason: %s' % ex)
except Exception as ex:
LOG.debug('An error occurred: %s' % ex)
pecan.abort(500)
try:
per_page = CONF.api.results_per_page
records = db.get_test_records(page_number, per_page, filters)
results = []
for r in records:
results.append({
'test_id': r.id,
'created_at': r.created_at,
'cpid': r.cpid,
'url': CONF.api.test_results_url % r.id
})
page = {'results': results,
'pagination': {
'current_page': page_number,
'total_pages': total_pages_number
}}
except Exception as ex:
LOG.debug('An error occurred during '
'operation with database: %s' % ex)
pecan.abort(400)
return page
class CapabilitiesController(rest.RestController):
"""/v1/capabilities handler.
This acts as a proxy for retrieving capability files
from the openstack/defcore Github repository.
"""
@pecan.expose('json')
def get(self):
"""Get a list of all available capabilities."""
try:
response = requests.get(CONF.api.github_api_capabilities_url)
LOG.debug("Response Status: %s / Used Requests Cache: %s" %
(response.status_code,
getattr(response, 'from_cache', False)))
if response.status_code == 200:
regex = re.compile('^[0-9]{4}\.[0-9]{2}\.json$')
capability_files = []
for rfile in response.json():
if rfile["type"] == "file" and regex.search(rfile["name"]):
capability_files.append(rfile["name"])
return capability_files
else:
LOG.warning('Github returned non-success HTTP '
'code: %s' % response.status_code)
pecan.abort(response.status_code)
except requests.exceptions.RequestException as e:
LOG.warning('An error occurred trying to get GitHub '
'repository contents: %s' % e)
pecan.abort(500)
@pecan.expose('json')
def get_one(self, file_name):
"""Handler for getting contents of specific capability file."""
github_url = ''.join((CONF.api.github_raw_base_url.rstrip('/'),
'/', file_name, ".json"))
try:
response = requests.get(github_url)
LOG.debug("Response Status: %s / Used Requests Cache: %s" %
(response.status_code,
getattr(response, 'from_cache', False)))
if response.status_code == 200:
return response.json()
else:
LOG.warning('Github returned non-success HTTP '
'code: %s' % response.status_code)
pecan.abort(response.status_code)
except requests.exceptions.RequestException as e:
LOG.warning('An error occurred trying to get GitHub '
'capability file contents: %s' % e)
pecan.abort(500)
class V1Controller(object):
"""Version 1 API controller root."""
results = ResultsController()
capabilities = CapabilitiesController()
auth = auth.AuthController()
profile = user.ProfileController()