aboutsummaryrefslogtreecommitdiffstats
path: root/opnfv_testapi/resources
diff options
context:
space:
mode:
authorpkaralis <pkaralis@intracom-telecom.com>2018-12-06 00:43:12 +0200
committerPanagiotis Karalis <pkaralis@intracom-telecom.com>2019-03-20 15:28:23 +0200
commitd0bbf3b8952379883550c6eb2062476a6d15043e (patch)
tree106f65b223054077279bda7ff988a73bea314a34 /opnfv_testapi/resources
parent5f20495d6e3ec984c4e86fd76399ddf0d042b336 (diff)
Enable Web Portal for ONAP results
The web portal needs to be able to read test results of the ONAP compliance program and display them. In order for the above goal to be achieved, the following two parts should be impacted: 1- A new front-end should be prepared in order to handle and display the results 2- The REST API should be extended in order to support the aforementioned operation. JIRA: DOVETAIL-669 Change-Id: I36bbb6e602a67020d7e27aedbfc776f5cf4f3dc3 Signed-off-by: pkaralis <pkaralis@intracom-telecom.com> Co-Authored-By: Stamatis Katsaounis <mokats@intracom-telecom.com>
Diffstat (limited to 'opnfv_testapi/resources')
-rw-r--r--opnfv_testapi/resources/application_handlers.py84
-rw-r--r--opnfv_testapi/resources/handlers.py15
-rw-r--r--opnfv_testapi/resources/result_handlers.py40
-rw-r--r--opnfv_testapi/resources/review_handlers.py119
-rw-r--r--opnfv_testapi/resources/review_models.py39
-rw-r--r--opnfv_testapi/resources/test_handlers.py47
6 files changed, 299 insertions, 45 deletions
diff --git a/opnfv_testapi/resources/application_handlers.py b/opnfv_testapi/resources/application_handlers.py
index 7cecd3e..7d823b8 100644
--- a/opnfv_testapi/resources/application_handlers.py
+++ b/opnfv_testapi/resources/application_handlers.py
@@ -6,15 +6,20 @@
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
+from datetime import datetime
import logging
import json
+import os
from tornado import web
from tornado import gen
from bson import objectid
+from slugify import slugify
+from PIL import Image
from opnfv_testapi.common.config import CONF
from opnfv_testapi.common import utils
+from opnfv_testapi.db import api as dbapi
from opnfv_testapi.resources import handlers
from opnfv_testapi.resources import application_models
from opnfv_testapi.tornado_swagger import swagger
@@ -34,19 +39,28 @@ class ApplicationsLogoHandler(GenericApplicationHandler):
@web.asynchronous
@gen.coroutine
def post(self):
- role = self.get_secure_cookie(auth_const.ROLE)
- if role.find('administrator') == -1:
- msg = 'Only administrator is allowed to upload logos'
- self.finish_request({'code': '-1', 'msg': msg})
- return
-
fileinfo = self.request.files['file'][0]
- fname = fileinfo['filename']
+ company_logo_name = self.request.arguments['company_name'][0]
+ extension_name = fileinfo['filename'].split('.')[-1]
+ company_logo_name = slugify(company_logo_name)
+ fileinfo['filename'] = company_logo_name
location = 'media/companies/'
- fh = open(location + fname, 'w')
+ full_name_path = location + company_logo_name + '.' + extension_name
+ fh = open(full_name_path, 'w')
fh.write(fileinfo['body'])
- msg = 'Successfully uploaded logo: ' + fname
- resp = {'code': '1', 'msg': msg}
+ fh.close()
+ img = Image.open(full_name_path)
+ if (img.size[0] > 165) or (img.size[1] > 40):
+ os.remove(full_name_path)
+ msg = 'The size of the image is not according to the compliance' \
+ ' program. Please try again, loading an image with proper' \
+ ' dimensions (Max Values: 165px width and 40px height).'
+ self.finish_request({'code': 403, 'msg': msg})
+ return
+
+ msg = 'Successfully uploaded logo: ' + company_logo_name
+ resp = {'code': 0, 'msg': msg,
+ 'filename': company_logo_name + '.' + extension_name}
self.finish_request(resp)
@@ -130,6 +144,8 @@ class ApplicationsCLHandler(GenericApplicationHandler):
openid = self.get_secure_cookie(auth_const.OPENID)
if openid:
self.json_args['owner'] = openid
+ if self.is_onap:
+ self.json_args['is_onap'] = 'true'
self._post()
@@ -138,22 +154,21 @@ class ApplicationsCLHandler(GenericApplicationHandler):
miss_fields = []
carriers = []
- role = self.get_secure_cookie(auth_const.ROLE)
- if role.find('administrator') == -1:
- self.finish_request({'code': '403', 'msg': 'Only administrator \
- is allowed to submit application.'})
- return
-
- query = {"openid": self.json_args['user_id']}
- table = "users"
- ret, msg = yield self._check_if_exists(table=table, query=query)
+ query = {'openid': self.json_args['owner']}
+ ret, msg = yield self._check_if_exists(table='users', query=query)
logging.debug('ret:%s', ret)
if not ret:
- self.finish_request({'code': '403', 'msg': msg})
+ self.finish_request({'code': 403, 'msg': msg})
+ return
+ query = {'test_id': self.json_args['test_id']}
+ ret, _ = yield self._check_if_exists(table=self.table, query=query)
+ if ret:
+ msg = 'An application for these test results already exists'
+ self.finish_request({'code': 403, 'msg': msg})
return
self._create(miss_fields=miss_fields, carriers=carriers)
- self._send_email()
+ # self._send_email()
def _send_email(self):
@@ -173,7 +188,6 @@ This is a new application:
Primary Email: {},
Primary Address: {},
Primary Phone: {},
- User ID Type: {},
User ID: {}
Best Regards,
@@ -188,19 +202,26 @@ CVP Team
data.prim_email,
data.prim_address,
data.prim_phone,
- data.id_type,
- data.user_id)
+ data.owner)
utils.send_email(subject, content)
class ApplicationsGURHandler(GenericApplicationHandler):
@swagger.operation(nickname="deleteAppById")
+ @gen.coroutine
def delete(self, id):
query = {'_id': objectid.ObjectId(id)}
+ application = yield dbapi.db_find_one(self.table, query)
+ test_id = application['test_id']
+ t_query = {'id': test_id}
+ yield dbapi.db_delete('reviews', {'test_id': test_id})
+ yield dbapi.db_update('tests', t_query,
+ {'$set': {'status': 'private'}})
self._delete(query=query)
@swagger.operation(nickname="updateApplicationById")
+ @web.asynchronous
def put(self, application_id):
"""
@description: update a single application by id
@@ -222,12 +243,23 @@ class ApplicationsGURHandler(GenericApplicationHandler):
logging.error('except:%s', e)
return
- @web.asynchronous
@gen.coroutine
def update(self, application_id, item, value):
self.json_args = {}
self.json_args[item] = value
- query = {'_id': application_id, 'owner':
+ query = {'_id': objectid.ObjectId(application_id), 'owner':
self.get_secure_cookie(auth_const.OPENID)}
db_keys = ['_id', 'owner']
+ if item == 'approved':
+ if value == 'true':
+ status = 'verified'
+ self.json_args['approve_date'] = str(datetime.now())
+ else:
+ status = 'review'
+ self.json_args['approve_date'] = ''
+ application = yield dbapi.db_find_one(self.table, query)
+ test_id = application['test_id']
+ t_query = {'id': test_id}
+ yield dbapi.db_update('tests', t_query,
+ {'$set': {'status': status}})
self._update(query=query, db_keys=db_keys)
diff --git a/opnfv_testapi/resources/handlers.py b/opnfv_testapi/resources/handlers.py
index e8c81f3..559e689 100644
--- a/opnfv_testapi/resources/handlers.py
+++ b/opnfv_testapi/resources/handlers.py
@@ -41,6 +41,7 @@ DEFAULT_REPRESENTATION = "application/json"
class GenericApiHandler(web.RequestHandler):
def __init__(self, application, request, **kwargs):
+ self.is_onap = False
super(GenericApiHandler, self).__init__(application, request, **kwargs)
self.json_args = None
self.table = None
@@ -52,6 +53,9 @@ class GenericApiHandler(web.RequestHandler):
self.db_scenarios = 'scenarios'
self.auth = self.settings["auth"]
+ def initialize(self, is_onap=False):
+ self.is_onap = is_onap
+
def get_int(self, key, value):
try:
value = int(value)
@@ -97,6 +101,11 @@ class GenericApiHandler(web.RequestHandler):
if role.find("reviewer") != -1:
query['$or'].append({"status": {"$ne": "private"}})
+ elif k == 'status':
+ if v.startswith('{'):
+ query[k] = json.loads(v)
+ else:
+ query[k] = v
elif k not in ['last', 'page', 'descend', 'per_page']:
query[k] = v
if date_range:
@@ -107,6 +116,8 @@ class GenericApiHandler(web.RequestHandler):
if 'start_date' in query and '$lt' not in query['start_date']:
query['start_date'].update({'$lt': str(datetime.now())})
+ query['is_onap'] = 'true' if self.is_onap else None
+
logging.debug("query:%s", query)
raise gen.Return((query))
@@ -184,7 +195,7 @@ class GenericApiHandler(web.RequestHandler):
if query and table:
data = yield dbapi.db_find_one(table, query)
if data:
- raise gen.Return((True, 'Data alreay exists. %s' % (query)))
+ raise gen.Return((True, 'Data already exists. %s' % (query)))
raise gen.Return((False, 'Data does not exist. %s' % (query)))
# @web.asynchronous
@@ -214,7 +225,7 @@ class GenericApiHandler(web.RequestHandler):
if res_op is None:
res = {self.table: data}
else:
- res = res_op(data, *args)
+ res = yield res_op(data, *args)
if page > 0:
res.update({
'pagination': {
diff --git a/opnfv_testapi/resources/result_handlers.py b/opnfv_testapi/resources/result_handlers.py
index 38109ad..9501bfd 100644
--- a/opnfv_testapi/resources/result_handlers.py
+++ b/opnfv_testapi/resources/result_handlers.py
@@ -20,6 +20,7 @@ from bson import objectid
from opnfv_testapi.common.config import CONF
from opnfv_testapi.common import message
from opnfv_testapi.common import raises
+from opnfv_testapi.db import api as dbapi
from opnfv_testapi.resources import handlers
from opnfv_testapi.resources import result_models
from opnfv_testapi.tornado_swagger import swagger
@@ -41,6 +42,7 @@ class GenericResultHandler(handlers.GenericApiHandler):
raises.BadRequest(message.must_int(key))
return value
+ @gen.coroutine
def set_query(self):
query = dict()
date_range = dict()
@@ -82,11 +84,15 @@ class GenericResultHandler(handlers.GenericApiHandler):
if 'start_date' in query and '$lt' not in query['start_date']:
query['start_date'].update({'$lt': str(datetime.now())})
- return query
+ query['is_onap'] = 'true' if self.is_onap else None
+
+ raise gen.Return((query))
class ResultsCLHandler(GenericResultHandler):
@swagger.operation(nickname="queryTestResults")
+ @web.asynchronous
+ @gen.coroutine
def get(self):
"""
@description: Retrieve result(s) for a test project
@@ -195,7 +201,8 @@ class ResultsCLHandler(GenericResultHandler):
'per_page': CONF.api_results_per_page
}
- self._list(query=self.set_query(), **limitations)
+ query = yield self.set_query()
+ yield self._list(query=query, **limitations)
@swagger.operation(nickname="createTestResult")
def post(self):
@@ -267,10 +274,19 @@ class ResultsUploadHandler(ResultsCLHandler):
results = results.split('\n')
result_ids = []
version = ''
+ vnf_type = None
+ vnf_checksum = None
for result in results:
if result == '':
continue
self.json_args = json.loads(result).copy()
+ openid = self.get_secure_cookie(auth_const.OPENID)
+ if openid:
+ self.json_args['owner'] = openid
+ if self.is_onap:
+ self.json_args['is_onap'] = 'true'
+ vnf_type = self.json_args['vnf_type']
+ vnf_checksum = self.json_args['vnf_checksum']
# the result files used in the first release of OVP did not
# specify an OVP version
if (self.json_args['version'] == 'master'
@@ -288,14 +304,30 @@ class ResultsUploadHandler(ResultsCLHandler):
with open(log_filename, "wb") as tar_out:
tar_out.write(fileinfo['body'])
resp = {'id': test_id, 'results': result_ids, 'version': version}
+ if vnf_type:
+ resp['vnf_type'] = vnf_type
+ resp['vnf_checksum'] = vnf_checksum
self.finish_request(resp)
class ResultsGURHandler(GenericResultHandler):
@swagger.operation(nickname='DeleteTestResultById')
+ @gen.coroutine
def delete(self, result_id):
- query = {'_id': objectid.ObjectId(result_id)}
- self._delete(query=query)
+ curr_user = self.get_secure_cookie(auth_const.OPENID)
+ curr_user_role = self.get_secure_cookie(auth_const.ROLE)
+ if curr_user is not None:
+ query = {'_id': objectid.ObjectId(result_id)}
+ test_data = yield dbapi.db_find_one(self.table, query)
+ if not test_data:
+ raises.NotFound(message.not_found(self.table, query))
+ if curr_user == test_data['owner'] or \
+ curr_user_role.find('administrator') != -1:
+ self._delete(query=query)
+ else:
+ raises.Forbidden(message.no_auth())
+ else:
+ raises.Unauthorized(message.no_auth())
@swagger.operation(nickname='getTestResultById')
def get(self, result_id):
diff --git a/opnfv_testapi/resources/review_handlers.py b/opnfv_testapi/resources/review_handlers.py
new file mode 100644
index 0000000..9731e0f
--- /dev/null
+++ b/opnfv_testapi/resources/review_handlers.py
@@ -0,0 +1,119 @@
+##############################################################################
+# Copyright (c) 2019 Intracom Telecom
+# mokats@intracom-telecom.com
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+from datetime import datetime
+import logging
+
+from tornado import web, gen
+
+from opnfv_testapi.common.config import CONF
+from opnfv_testapi.common import message, raises
+from opnfv_testapi.db import api as dbapi
+from opnfv_testapi.resources import handlers, review_models
+from opnfv_testapi.tornado_swagger import swagger
+from opnfv_testapi.ui.auth import constants as auth_const
+
+
+class GenericReviewHandler(handlers.GenericApiHandler):
+ def __init__(self, review, request, **kwargs):
+ super(GenericReviewHandler, self).__init__(review, request, **kwargs)
+ self.table = 'reviews'
+ self.table_cls = review_models.Review
+
+
+class ReviewsCLHandler(GenericReviewHandler):
+ @swagger.operation(nickname="queryReviews")
+ @web.asynchronous
+ @gen.coroutine
+ def get(self):
+ def descend_limit():
+ descend = self.get_query_argument('descend', 'true')
+ return -1 if descend.lower() == 'true' else 1
+
+ def last_limit():
+ return self.get_int('last', self.get_query_argument('last', 0))
+
+ def page_limit():
+ return self.get_int('page', self.get_query_argument('page', 0))
+
+ limitations = {
+ 'sort': {'_id': descend_limit()},
+ 'last': last_limit(),
+ 'page': page_limit(),
+ 'per_page': CONF.api_results_per_page
+ }
+
+ query = yield self.set_query()
+ yield self._list(query=query, **limitations)
+ logging.debug('list end')
+
+ @swagger.operation(nickname="createReview")
+ @web.asynchronous
+ def post(self):
+ openid = self.get_secure_cookie(auth_const.OPENID)
+ if openid:
+ self.json_args['reviewer_openid'] = openid
+
+ if self.json_args['outcome'] is None:
+ self._del()
+ else:
+ self._post()
+
+ @gen.coroutine
+ def _post(self):
+ query = {'openid': self.json_args['reviewer_openid']}
+ user = yield dbapi.db_find_one('users', query)
+ if not user:
+ raises.Forbidden(message.unauthorized())
+ role = self.get_secure_cookie(auth_const.ROLE)
+ if 'reviewer' not in role.split(','):
+ raises.Unauthorized(message.no_auth())
+ test = yield dbapi.db_find_one(
+ 'tests', {'id': self.json_args['test_id']})
+ if test['owner'] == self.json_args['reviewer_openid']:
+ self.finish_request({'code': 403,
+ 'msg': 'No permision to review own results'})
+ return
+ query = {
+ 'reviewer_openid': self.json_args['reviewer_openid'],
+ 'test_id': self.json_args['test_id']
+ }
+ review = yield dbapi.db_find_one(self.table, query)
+ if review:
+ if review['outcome'] != self.json_args['outcome']:
+ yield dbapi.db_update(self.table, query,
+ {'$set': {
+ 'outcome': self.json_args['outcome'],
+ 'creation_date': datetime.now()}})
+ self.finish_request()
+ else:
+ self.json_args['reviewer_name'] = user['fullname']
+ self.json_args['reviewer_email'] = user['email']
+ self._create(miss_fields=[], carriers=[])
+
+ @gen.coroutine
+ def _del(self):
+ query = {'openid': self.json_args['reviewer_openid']}
+ user = yield dbapi.db_find_one('users', query)
+ if not user:
+ raises.Forbidden(message.unauthorized())
+ role = self.get_secure_cookie(auth_const.ROLE)
+ if 'reviewer' not in role.split(','):
+ raises.Unauthorized(message.no_auth())
+ test = yield dbapi.db_find_one(
+ 'tests', {'id': self.json_args['test_id']})
+ if test['owner'] == self.json_args['reviewer_openid']:
+ self.finish_request({'code': 403,
+ 'msg': 'No permision to review own results'})
+ return
+ query = {
+ 'reviewer_openid': self.json_args['reviewer_openid'],
+ 'test_id': self.json_args['test_id']
+ }
+ yield dbapi.db_delete(self.table, query)
+ self.finish_request()
diff --git a/opnfv_testapi/resources/review_models.py b/opnfv_testapi/resources/review_models.py
new file mode 100644
index 0000000..2aaa62c
--- /dev/null
+++ b/opnfv_testapi/resources/review_models.py
@@ -0,0 +1,39 @@
+##############################################################################
+# Copyright (c) 2019
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+from opnfv_testapi.resources import models
+from opnfv_testapi.tornado_swagger import swagger
+
+from datetime import datetime
+
+
+@swagger.model()
+class Review(models.ModelBase):
+ def __init__(self, _id=None, test_id=None, reviewer_openid=None,
+ reviewer_email=None, reviewer_name=None, creation_date=None,
+ outcome=None):
+ self._id = _id
+ self.test_id = test_id
+ self.reviewer_openid = reviewer_openid
+ self.reviewer_email = reviewer_email
+ self.reviewer_name = reviewer_name
+ self.creation_date = datetime.now()
+ self.outcome = outcome
+
+
+@swagger.model()
+class Reviews(models.ModelBase):
+ """
+ @property reviews:
+ @ptype tests: C{list} of L{Review}
+ """
+ def __init__(self):
+ self.reviews = list()
+
+ @staticmethod
+ def attr_parser():
+ return {'reviews': Review}
diff --git a/opnfv_testapi/resources/test_handlers.py b/opnfv_testapi/resources/test_handlers.py
index 5ecb176..7ab20dc 100644
--- a/opnfv_testapi/resources/test_handlers.py
+++ b/opnfv_testapi/resources/test_handlers.py
@@ -78,10 +78,28 @@ class TestsCLHandler(GenericTestHandler):
if curr_user is None:
raises.Unauthorized(message.no_auth())
+ review = self.request.query_arguments.pop('review', None)
query = yield self.set_query()
- yield self._list(query=query, **limitations)
+ if review:
+ yield self._list(query=query, res_op=self.check_review,
+ **limitations)
+ else:
+ yield self._list(query=query, **limitations)
logging.debug('list end')
+ @gen.coroutine
+ def check_review(self, data, *args):
+ current_user = self.get_secure_cookie(auth_const.OPENID)
+ for test in data:
+ query = {'reviewer_openid': current_user, 'test_id': test['id']}
+ ret = yield dbapi.db_find_one('reviews', query)
+ if ret:
+ test['voted'] = 'true'
+ else:
+ test['voted'] = 'false'
+
+ raise gen.Return({self.table: data})
+
@swagger.operation(nickname="createTest")
@web.asynchronous
def post(self):
@@ -111,6 +129,8 @@ class TestsCLHandler(GenericTestHandler):
self.finish_request({'code': '403', 'msg': msg})
return
+ if self.is_onap:
+ self.json_args['is_onap'] = 'true'
self._create(miss_fields=miss_fields, carriers=carriers)
@@ -149,7 +169,6 @@ class TestsGURHandler(GenericTestHandler):
raise gen.Return('API response validation enabled')
@swagger.operation(nickname="deleteTestById")
- @web.asynchronous
@gen.coroutine
def delete(self, test_id):
curr_user = self.get_secure_cookie(auth_const.OPENID)
@@ -161,6 +180,9 @@ class TestsGURHandler(GenericTestHandler):
raises.NotFound(message.not_found(self.table, query))
if curr_user == test_data['owner'] or \
curr_user_role.find('administrator') != -1:
+ yield dbapi.db_delete('applications',
+ {'test_id': test_data['id']})
+ yield dbapi.db_delete('reviews', {'test_id': test_data['id']})
self._delete(query=query)
else:
raises.Forbidden(message.no_auth())
@@ -198,7 +220,7 @@ class TestsGURHandler(GenericTestHandler):
if query and table:
data = yield dbapi.db_find_one(table, query)
if data:
- raise gen.Return((True, 'Data alreay exists. %s' % (query),
+ raise gen.Return((True, 'Data already exists. %s' % (query),
data.get("openid")))
raise gen.Return((False, 'Data does not exist. %s' % (query), None))
@@ -255,9 +277,9 @@ class TestsGURHandler(GenericTestHandler):
return
if item == "status":
- if value in {'approved', 'not approved'}:
+ if value == 'verified':
if test['status'] == 'private':
- msg = 'Not allowed to approve/not approve'
+ msg = 'Not allowed to verify'
self.finish_request({'code': 403, 'msg': msg})
return
@@ -272,7 +294,7 @@ class TestsGURHandler(GenericTestHandler):
self.finish_request({'code': 403, 'msg': msg})
return
- if not test['sut_label']:
+ if not self.is_onap and not test['sut_label']:
msg = 'Please fill out SUT version before submission'
self.finish_request({'code': 403, 'msg': msg})
return
@@ -284,8 +306,7 @@ class TestsGURHandler(GenericTestHandler):
'id': test['id'],
'$or': [
{'status': 'review'},
- {'status': 'approved'},
- {'status': 'not approved'}
+ {'status': 'verified'}
]
}
record = yield dbapi.db_find_one("tests", test_query)
@@ -311,12 +332,12 @@ class TestsGURHandler(GenericTestHandler):
logging.debug('check review')
query['user_id'] = user
data = yield dbapi.db_find_one('applications', query)
- if not data:
- logging.debug('not found')
+ if data:
+ logging.debug('results are bound to an application')
raise gen.Return((False, message.no_auth()))
- if value == "approve" or value == "not approved":
- logging.debug('check approve')
- query['role'] = {"$regex": ".*reviewer.*"}
+ if value == "verified":
+ logging.debug('check verify')
+ query['role'] = {"$regex": ".*administrator.*"}
query['openid'] = user
data = yield dbapi.db_find_one('users', query)
if not data: