Add restful server API in QTIP which can be called by Yardstick. 15/18015/4
authorzhifeng.jiang <jiang.zhifeng@zte.com.cn>
Thu, 4 Aug 2016 04:14:20 +0000 (12:14 +0800)
committerzhifeng.jiang <jiang.zhifeng@zte.com.cn>
Sat, 13 Aug 2016 03:09:30 +0000 (11:09 +0800)
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>
requirements.txt
restful_server/__init__.py [new file with mode: 0644]
restful_server/db.py [new file with mode: 0644]
restful_server/qtip_server.py [new file with mode: 0644]
tests/qtip_server_test.py [new file with mode: 0644]

index 358b7d0..af07083 100644 (file)
@@ -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 (file)
index 0000000..e69de29
diff --git a/restful_server/db.py b/restful_server/db.py
new file mode 100644 (file)
index 0000000..b8314de
--- /dev/null
@@ -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 (file)
index 0000000..5958836
--- /dev/null
@@ -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 (file)
index 0000000..31aa96d
--- /dev/null
@@ -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)