summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorzhifeng.jiang <jiang.zhifeng@zte.com.cn>2016-08-04 12:14:20 +0800
committerzhifeng.jiang <jiang.zhifeng@zte.com.cn>2016-08-13 11:09:30 +0800
commit2c78b093d68e901d3f768029b2818f856f74e118 (patch)
treedab4894b70a2fb9d3078e88c77c6e40ea325caf7
parent9197285af7b9ffe49eb22902477c559cef35b43f (diff)
Add restful server API in QTIP which can be called by Yardstick.
modification: Add restful server API. Include needed packages in requirement file. Add unit tests for restful server api. JIRA:QTIP-97 Change-Id: I5306d5dd76ee818fef46d0e7b7392f2df2982552 Signed-off-by: zhifeng.jiang <jiang.zhifeng@zte.com.cn>
-rw-r--r--requirements.txt3
-rw-r--r--restful_server/__init__.py0
-rw-r--r--restful_server/db.py48
-rw-r--r--restful_server/qtip_server.py138
-rw-r--r--tests/qtip_server_test.py78
5 files changed, 267 insertions, 0 deletions
diff --git a/requirements.txt b/requirements.txt
index 358b7d0d..af07083d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,3 +7,6 @@ python-cinderclient==1.4.0
python-heatclient==0.6.0
python-keystoneclient==1.6.0
reportlab==3.0
+Flask==0.11.1
+Flask-RESTful==0.3.5
+flask-restful-swagger==0.19
diff --git a/restful_server/__init__.py b/restful_server/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/restful_server/__init__.py
diff --git a/restful_server/db.py b/restful_server/db.py
new file mode 100644
index 00000000..b8314de2
--- /dev/null
+++ b/restful_server/db.py
@@ -0,0 +1,48 @@
+##############################################################################
+# Copyright (c) 2016 ZTE Corp and others.
+#
+# 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 uuid
+
+jobs = {}
+
+
+def create_job(args):
+ if len(filter(lambda x: jobs[x]['state'] == 'processing', jobs.keys())) > 0:
+ return None
+ else:
+ job = {'job_id': str(uuid.uuid4()),
+ 'installer_type': args["installer_type"],
+ 'installer_ip': args["installer_ip"],
+ 'pod_name': args["pod_name"],
+ 'suite_name': args["suite_name"],
+ 'deadline': args["deadline"],
+ 'type': args["type"],
+ 'start-time': str(datetime.now()),
+ 'end-time': None,
+ 'state': 'processing',
+ 'state_detail': [],
+ 'result': []}
+ jobs[job['job_id']] = job
+ return job['job_id']
+
+
+def delete_job(job_id):
+ if job_id in jobs.keys():
+ jobs[job_id]['end_time'] = datetime.now()
+ jobs[job_id]['state'] = 'terminated'
+ return True
+ else:
+ return False
+
+
+def get_job_info(job_id):
+ if job_id in jobs.keys():
+ return jobs[job_id]
+ else:
+ return None
diff --git a/restful_server/qtip_server.py b/restful_server/qtip_server.py
new file mode 100644
index 00000000..59588363
--- /dev/null
+++ b/restful_server/qtip_server.py
@@ -0,0 +1,138 @@
+##############################################################################
+# Copyright (c) 2016 ZTE Corp and others.
+#
+# 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 flask import Flask, abort
+from flask_restful import Api, Resource, fields, reqparse
+from flask_restful_swagger import swagger
+import db
+
+
+app = Flask(__name__)
+api = swagger.docs(Api(app), apiVersion='0.1')
+
+
+@swagger.model
+class JobModel:
+ resource_fields = {
+ 'installer_type': fields.String,
+ 'installer_ip': fields.String,
+ 'deadline': fields.Integer,
+ 'pod_name': fields.String,
+ 'suite_name': fields.String,
+ 'type': fields.String
+ }
+ required = ['installer_type', 'install_ip']
+
+
+@swagger.model
+class JobResponseModel:
+ resource_fields = {
+ 'job_id': fields.String
+ }
+
+
+class Job(Resource):
+ @swagger.operation(
+ notes='get a job by ID',
+ nickname='get',
+ parameters=[],
+ responseMessages=[
+ {
+ "code": 200,
+ "message": "Job detail info."
+ },
+ {
+ "code": 404,
+ "message": "Can't not find the job id XXXXXXX"
+ }
+ ]
+ )
+ def get(self, id):
+ ret = db.get_job_info(id)
+ return ret if ret else abort(404, " Can't not find the job id %s" % id)
+
+ @swagger.operation(
+ notes='delete a job by ID',
+ nickname='delete',
+ parameters=[],
+ responseMessages=[
+ {
+ "code": 200,
+ "message": "Delete successfully"
+ },
+ {
+ "code": 404,
+ "message": "Can not find job_id XXXXXXXXX"
+ }
+ ]
+ )
+ def delete(self, id):
+ ret = db.delete_job(id)
+ return {'result': "Delete successfully"} if ret else abort(404, "Can not find job_id %s" % id)
+
+
+class JobList(Resource):
+ @swagger.operation(
+ note='create a job with parameters',
+ nickname='create',
+ parameters=[
+ {
+ "name": "body",
+ "description": """
+"installer_type": The installer type, for example fuel, compass..,
+
+"installer_ip": The installer ip of the pod,
+
+"deadline": If specified, the maximum duration in minutes
+for any single test iteration, default is '10',
+
+"pod_name": If specified, the Pod name, default is 'default',
+
+"suite_name": If specified, Test suite name, for example 'compute', 'network', 'storage', 'all',
+default is 'all'
+"type": BM or VM,default is 'BM'
+ """,
+ "required": True,
+ "type": "JobModel",
+ "paramType": "body"
+ }
+ ],
+ type=JobResponseModel.__name__,
+ responseMessages=[
+ {
+ "code": 200,
+ "message": "Job submitted"
+ },
+ {
+ "code": 400,
+ "message": "Missing configuration data"
+ },
+ {
+ "code": 409,
+ "message": "It already has one job running now!"
+ }
+ ]
+ )
+ def post(self):
+ parser = reqparse.RequestParser()
+ parser.add_argument('installer_type', type=str, required=True, help='Installer_type is required')
+ parser.add_argument('installer_ip', type=str, required=True, help='Installer_ip is required')
+ parser.add_argument('deadline', type=int, required=False, default=10, help='dealine should be integer')
+ parser.add_argument('pod_name', type=str, required=False, default='default', help='pod_name should be string')
+ parser.add_argument('suite_name', type=str, required=False, default='all', help='suite_name should be string')
+ parser.add_argument('type', type=str, required=False, default='BM', help='type should be BM, VM and ALL')
+ args = parser.parse_args()
+ ret = db.create_job(args)
+ return {'job_id': str(ret)} if ret else abort(409, 'message:It already has one job running now!')
+
+
+api.add_resource(JobList, '/api/v1.0/jobs')
+api.add_resource(Job, '/api/v1.0/jobs/<string:id>')
+
+if __name__ == "__main__":
+ app.run(debug=True)
diff --git a/tests/qtip_server_test.py b/tests/qtip_server_test.py
new file mode 100644
index 00000000..31aa96dc
--- /dev/null
+++ b/tests/qtip_server_test.py
@@ -0,0 +1,78 @@
+import restful_server.qtip_server as server
+import pytest
+import json
+
+
+@pytest.fixture
+def app():
+ return server.app
+
+
+@pytest.fixture
+def app_client(app):
+ client = app.test_client()
+ return client
+
+
+class TestClass:
+ @pytest.mark.parametrize("body, expected", [
+ ({'installer_type': 'fuel',
+ 'installer_ip': '10.20.0.2'},
+ {'job_id': '',
+ 'installer_type': 'fuel',
+ 'installer_ip': '10.20.0.2',
+ 'pod_name': 'default',
+ 'suite_name': 'all',
+ 'deadline': 10,
+ 'type': 'BM',
+ 'state': 'processing',
+ 'state_detail': [],
+ 'result': []}),
+ ({'installer_type': 'fuel',
+ 'installer_ip': '10.20.0.2',
+ 'pod_name': 'zte-pod1',
+ 'deadline': 20,
+ 'suite_name': 'compute',
+ 'type': 'VM'},
+ {'job_id': '',
+ 'installer_type': 'fuel',
+ 'installer_ip': '10.20.0.2',
+ 'pod_name': 'zte-pod1',
+ 'suite_name': 'compute',
+ 'deadline': 20,
+ 'type': 'VM',
+ 'state': 'processing',
+ 'state_detail': [],
+ 'result': []})
+ ])
+ def test_post_get_delete_job_successful(self, app_client, body, expected):
+ reply = app_client.post("/api/v1.0/jobs", data=body)
+ print reply.data
+ id = json.loads(reply.data)['job_id']
+ expected['job_id'] = id
+ get_reply = app_client.get("/api/v1.0/jobs/%s" % id)
+ reply_data = json.loads(get_reply.data)
+ assert len(filter(lambda x: reply_data[x] == expected[x], expected.keys())) == len(expected)
+ delete_reply = app_client.delete("/api/v1.0/jobs/%s" % id)
+ assert "successful" in delete_reply.data
+
+ @pytest.mark.parametrize("body, expected", [
+ ([{'installer_type': 'fuel',
+ 'installer_ip': '10.20.0.2'},
+ {'installer_type': 'compass',
+ 'installer_ip': '192.168.20.50'}],
+ ['job_id',
+ 'It already has one job running now!']),
+ ([{'installer_type': 'fuel',
+ 'installer_ip': '10.20.0.2'},
+ {'installer_type': 'compass',
+ 'insta_ip': '192.168.20.50'}],
+ ['job_id',
+ 'Installer_ip is required'])
+ ])
+ def test_post_two_jobs_unsuccessful(self, app_client, body, expected):
+ reply_1 = app_client.post("/api/v1.0/jobs", data=body[0])
+ reply_2 = app_client.post("/api/v1.0/jobs", data=body[1])
+ assert expected[0] in json.loads(reply_1.data).keys()
+ app_client.delete("/api/v1.0/jobs/%s" % json.loads(reply_1.data)['job_id'])
+ assert expected[1] in json.dumps(reply_2.data)