Merge "Add API endpoint for retrieving capabilities"

This commit is contained in:
Jenkins 2015-05-04 18:42:42 +00:00 committed by Gerrit Code Review
commit cd536e7398
8 changed files with 211 additions and 1 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ ChangeLog
build/
cover/
dist
*.sqlite

View File

@ -135,6 +135,15 @@
# The format for start_date and end_date parameters (string value)
#input_date_format = %Y-%m-%d %H:%M:%S
# The GitHub API URL of the repository and location of the DefCore
# capability files. This URL is used to get a listing of all capability
# files.
#github_api_capabilities_url = https://api.github.com/repos/openstack/defcore/contents
# The base URL that is used for retrieving specific capability files.
# Capability file names will be appended to this URL to get the contents
# of that file.
#github_raw_base_url = https://raw.githubusercontent.com/openstack/defcore/master/
[database]

View File

@ -68,6 +68,20 @@ API_OPTS = [
default='http://refstack.net/output.html?test_id=%s',
help='Template for test result url.'
),
cfg.StrOpt('github_api_capabilities_url',
default='https://api.github.com'
'/repos/openstack/defcore/contents',
help='The GitHub API URL of the repository and location of '
'the DefCore capability files. This URL is used to get '
'a listing of all capability files.'
),
cfg.StrOpt('github_raw_base_url',
default='https://raw.githubusercontent.com'
'/openstack/defcore/master/',
help='This is the base URL that is used for retrieving '
'specific capability files. Capability file names will '
'be appended to this URL to get the contents of that file.'
)
]
CONF = cfg.CONF

View File

@ -16,10 +16,14 @@
"""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 refstack import db
from refstack.api import constants as const
@ -43,6 +47,8 @@ CTRLS_OPTS = [
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', expire_after=600)
class BaseRestControllerWithValidation(rest.RestController):
@ -182,8 +188,62 @@ class ResultsController(BaseRestControllerWithValidation):
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:
json = response.json()
regex = re.compile('^[0-9]{4}\.[0-9]{2}\.json$')
capability_files = []
for rfile in 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()

View File

@ -18,6 +18,7 @@
import json
import uuid
import httmock
from oslo_config import fixture as config_fixture
import six
import webtest.app
@ -220,3 +221,37 @@ class TestResultsController(api.FunctionalTest):
self.assertEqual(len(filtering_results), 3)
for r in slice_results:
self.assertEqual(r, filtering_results)
class TestCapabilitiesController(api.FunctionalTest):
"""Test case for CapabilitiesController."""
URL = '/v1/capabilities/'
def test_get_capability_list(self):
@httmock.all_requests
def github_api_mock(url, request):
headers = {'content-type': 'application/json'}
content = [{'name': '2015.03.json', 'type': 'file'},
{'name': '2015.next.json', 'type': 'file'},
{'name': '2015.03', 'type': 'dir'}]
content = json.dumps(content)
return httmock.response(200, content, headers, None, 5, request)
with httmock.HTTMock(github_api_mock):
actual_response = self.get_json(self.URL)
expected_response = ['2015.03.json']
self.assertEqual(expected_response, actual_response)
def test_get_capability_file(self):
@httmock.all_requests
def github_mock(url, request):
content = {'foo': 'bar'}
return httmock.response(200, content, None, None, 5, request)
url = self.URL + "2015.03"
with httmock.HTTMock(github_mock):
actual_response = self.get_json(url)
expected_response = {'foo': 'bar'}
self.assertEqual(expected_response, actual_response)

View File

@ -15,9 +15,14 @@
"""Tests for API's controllers"""
import json
import sys
import httmock
import mock
from oslo_config import fixture as config_fixture
from oslotest import base
import requests
from refstack.api import constants as const
from refstack.api import utils as api_utils
@ -25,6 +30,15 @@ from refstack.api.controllers import root
from refstack.api.controllers import v1
def safe_json_dump(content):
if isinstance(content, (dict, list)):
if sys.version_info[0] == 3:
content = bytes(json.dumps(content), 'utf-8')
else:
content = json.dumps(content)
return content
class RootControllerTestCase(base.BaseTestCase):
def test_index(self):
@ -230,6 +244,81 @@ class ResultsControllerTestCase(base.BaseTestCase):
db_get_test.assert_called_once_with(page_number, per_page, filters)
class CapabilitiesControllerTestCase(base.BaseTestCase):
def setUp(self):
super(CapabilitiesControllerTestCase, self).setUp()
self.controller = v1.CapabilitiesController()
def test_get_capabilities(self):
"""Test when getting a list of all capability files."""
@httmock.all_requests
def github_api_mock(url, request):
headers = {'content-type': 'application/json'}
content = [{'name': '2015.03.json', 'type': 'file'},
{'name': '2015.next.json', 'type': 'file'},
{'name': '2015.03', 'type': 'dir'}]
content = safe_json_dump(content)
return httmock.response(200, content, headers, None, 5, request)
with httmock.HTTMock(github_api_mock):
result = self.controller.get()
self.assertEqual(['2015.03.json'], result)
@mock.patch('pecan.abort')
def test_get_capabilities_error_code(self, mock_abort):
"""Test when the HTTP status code isn't a 200 OK. The status should
be propogated."""
@httmock.all_requests
def github_api_mock(url, request):
content = {'title': 'Not Found'}
return httmock.response(404, content, None, None, 5, request)
with httmock.HTTMock(github_api_mock):
self.controller.get()
mock_abort.assert_called_with(404)
@mock.patch('requests.get')
@mock.patch('pecan.abort')
def test_get_capabilities_exception(self, mock_abort, mock_request):
"""Test when the GET request raises an exception."""
mock_request.side_effect = requests.exceptions.RequestException()
self.controller.get()
mock_abort.assert_called_with(500)
def test_get_capability_file(self):
"""Test when getting a specific capability file"""
@httmock.all_requests
def github_mock(url, request):
content = {'foo': 'bar'}
return httmock.response(200, content, None, None, 5, request)
with httmock.HTTMock(github_mock):
result = self.controller.get_one('2015.03')
self.assertEqual({'foo': 'bar'}, result)
@mock.patch('pecan.abort')
def test_get_capability_file_error_code(self, mock_abort):
"""Test when the HTTP status code isn't a 200 OK. The status should
be propogated."""
@httmock.all_requests
def github_api_mock(url, request):
content = {'title': 'Not Found'}
return httmock.response(404, content, None, None, 5, request)
with httmock.HTTMock(github_api_mock):
self.controller.get_one('2010.03')
mock_abort.assert_called_with(404)
@mock.patch('requests.get')
@mock.patch('pecan.abort')
def test_get_capability_file_exception(self, mock_abort, mock_request):
"""Test when the GET request raises an exception."""
mock_request.side_effect = requests.exceptions.RequestException()
self.controller.get_one('2010.03')
mock_abort.assert_called_with(500)
class BaseRestControllerWithValidationTestCase(base.BaseTestCase):
def setUp(self):

View File

@ -8,6 +8,7 @@ oslo.log
pecan>=0.8.2
pyOpenSSL==0.13
pycrypto>=2.6
requests==1.2.3
requests>=2.2.0,!=2.4.0
requests-cache>=0.4.9
jsonschema>=2.0.0,<3.0.0
PyMySQL>=0.6.2,!=0.6.4

View File

@ -2,6 +2,7 @@ coverage>=3.6
pep8==1.5.7
pyflakes==0.8.1
flake8==2.2.4
httmock
mock
oslotest>=1.2.0 # Apache-2.0
python-subunit>=0.0.18