Skip to content
This repository was archived by the owner on Feb 5, 2020. It is now read-only.

Commit 19fbc30

Browse files
authored
Merge pull request #5 from todorez/wsgi
convert codi to wsgi, add retries parameters and tests for the RESTful API
2 parents 2e2c7b2 + 3f63126 commit 19fbc30

File tree

11 files changed

+379
-48
lines changed

11 files changed

+379
-48
lines changed

.travis.yml

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
language: python
2+
python:
3+
- "3.5"
4+
5+
sudo: required
6+
services:
7+
- docker
8+
9+
env:
10+
global:
11+
- DOCKER_VERSION=1.10.1-0~trusty
12+
13+
before_install:
14+
- apt-cache madison docker-engine
15+
- sudo apt-get -o Dpkg::Options::="--force-confnew" install -y -qq docker-engine=${DOCKER_VERSION}
16+
17+
script:
18+
# configure crops-py dependencies
19+
- pip install -e .
20+
- pip install --upgrade pip
21+
- pip install flask docker-py rethinkdb gunicorn
22+
23+
# install & configure database
24+
- source /etc/lsb-release
25+
- echo "deb http://download.rethinkdb.com/apt $DISTRIB_CODENAME main" | sudo tee /etc/apt/sources.list.d/rethinkdb.list
26+
- sudo wget -qO- https://download.rethinkdb.com/apt/pubkey.gpg | sudo apt-key add -
27+
- sudo apt-get update -qq
28+
- sudo apt-get install -y -qq rethinkdb curl
29+
- sudo apt-get remove --purge python2.7 -y -qq
30+
- sudo cp /etc/rethinkdb/default.conf.sample /etc/rethinkdb/instances.d/default.conf
31+
- sudo /etc/init.d/rethinkdb start
32+
33+
# install toolchain descriptor
34+
- sudo mkdir -p /opt/poky/.crops
35+
- wget -q -P /tmp http://crops.github.io/toolchain-json/poky-glibc-x86_64-core-image-sato-i586-toolchain-2.1-json.sh
36+
- chmod 755 /tmp/poky-glibc-x86_64-core-image-sato-i586-toolchain-2.1-json.sh
37+
- sudo /tmp/poky-glibc-x86_64-core-image-sato-i586-toolchain-2.1-json.sh -d /opt/poky -y
38+
39+
# run tests
40+
- python tests.py
41+
42+
notifications:
43+
email:
44+
recipients:
45+
on_success: change
46+
on_failure: always

codi/codi.py

+18-14
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from flask import Response
2020
from utils.globs import config
2121
from utils.docker import dcrops
22+
from codi import codiDB
2223
import argparse
2324

2425
class Codi() :
@@ -27,10 +28,10 @@ class Codi() :
2728
Args: app (Flask): Instance of a Flask application
2829
'''
2930

30-
def __init__(self, flask_app, codi_db):
31+
def __init__(self, flask_app):
3132
'''Initialize Codi with a flask app instance variable'''
3233
self.app = flask_app
33-
self.db = codi_db
34+
self.db = codiDB.CodiDB(config.CODI_DB)
3435

3536
def list_api(self):
3637
'''Show CODI API [GET]
@@ -63,13 +64,18 @@ def add_toolchain(self):
6364
'''
6465
if request.method == 'POST':
6566
json_data = request.get_json()
66-
#duplicate ids will not be inserted
6767
json_data["client_ip"] = request.remote_addr
68-
self.db.db_insert(config.TOOLCHAINS_TBL, json_data)
69-
return 'Success'
68+
response = self.db.db_insert(config.TOOLCHAINS_TBL, json_data)
69+
if response is not None:
70+
response = Response(json.dumps(list(response)), mimetype='application/json')
71+
response.status_code = 200
72+
else:
73+
response = Response(json.dumps(list(response)), mimetype='application/json')
74+
response.status_code = 400
7075
else:
71-
print("Unable to get JSON data")
72-
return 'Error'
76+
response = Response(json.dumps(list(response)), mimetype='application/json')
77+
response.status_code = 400
78+
return response
7379

7480
def find_image(self):
7581
'''Search for a toolchain image in Docker repository [GET]
@@ -97,11 +103,9 @@ def pull_image(self):
97103
cli = dcrops.docker_connect(config.DOCKER_SOCKET)
98104
image = request.args.get("image")
99105
if image is not None:
100-
for response in cli.pull(image, stream=True):
101-
print(response.decode("utf-8"))
102-
temp = temp + response.decode("utf-8")
106+
cli.pull(image, stream=False)
103107
cli.close()
104-
return Response(json.dumps(temp), mimetype='application/json')
108+
return Response(json.dumps("Success"), mimetype='application/json')
105109
else:
106110
cli.close()
107111
return "Error: Image not provided"
@@ -139,9 +143,9 @@ def get_arg_parser(self):
139143
returns: codi arguments
140144
'''
141145
parser = argparse.ArgumentParser(
142-
description='CODI command line arguments')
146+
description='CODI command line arguments')
143147
parser.add_argument('--ip', default="0.0.0.0",
144-
help='codi ip address (default: 0.0.0.0)')
148+
help='codi ip address (default: 0.0.0.0)')
145149
parser.add_argument('--port', default=10000, type=int,
146-
help='codi port (default: 10000)')
150+
help='codi port (default: 10000)')
147151
return parser.parse_args()

codi/codiDB.py

+64-28
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515

1616
from utils.globs import config
1717
import rethinkdb as r
18+
import signal
1819
import json
20+
import time
21+
import sys
22+
import os
1923

2024
class CodiDB() :
2125
'''CodiDB is used as a persistent storage for tracking
@@ -24,51 +28,83 @@ class CodiDB() :
2428

2529
def __init__(self, db_name):
2630
self.db_name = db_name
27-
self.db_create()
28-
self.db_table_create(config.TOOLCHAINS_TBL)
31+
create = self.db_create()
32+
if create is not None:
33+
if self.db_table_create(config.TOOLCHAINS_TBL) is None:
34+
print("Unable to initialize database table.Exiting")
35+
if ("gunicorn" in os.environ.get("SERVER_SOFTWARE", "")):
36+
os.kill(os.getppid(), signal.SIGTERM)
37+
else:
38+
sys.exit(0)
39+
else:
40+
print("Couldn't connect to the database")
41+
if ("gunicorn" in os.environ.get("SERVER_SOFTWARE", "")):
42+
os.kill(os.getppid(), signal.SIGTERM)
43+
else:
44+
sys.exit(0)
2945

3046
def __db_connect_(self):
31-
return r.connect('localhost', 28015).repl()
47+
try:
48+
conn = r.connect('localhost', 28015).repl()
49+
except r.ReqlDriverError:
50+
return False
51+
return conn
52+
53+
def __db_connect_retry(self, retries):
54+
while(bool(retries)):
55+
conn = self.__db_connect_();
56+
if (not conn):
57+
retries -= 1
58+
time.sleep(2)
59+
else:
60+
return conn
3261

3362
def db_create(self):
34-
c = self.__db_connect_()
63+
c = self.__db_connect_retry(config.DB_RETRIES)
3564
res = None
36-
if self.db_name not in r.db_list().run(c):
37-
res = r.db_create(self.db_name).run(c)
38-
c.close()
65+
if c :
66+
if self.db_name not in r.db_list().run(c):
67+
res = r.db_create(self.db_name).run(c)
68+
else:
69+
res = False
70+
c.close()
3971
return res
4072

4173
def db_table_create(self, table_name):
42-
c = self.__db_connect_()
74+
c = self.__db_connect_retry(config.DB_RETRIES)
4375
res = None
44-
if table_name not in r.db(self.db_name).table_list().run(c):
45-
res =r.db(self.db_name).table_create(table_name).run(c)
46-
c.close()
76+
if c:
77+
if table_name not in r.db(self.db_name).table_list().run(c):
78+
res =r.db(self.db_name).table_create(table_name).run(c)
79+
else:
80+
res = False
81+
c.close()
4782
return res
4883

4984
def db_insert(self, table, entry):
50-
c = self.__db_connect_()
51-
res = r.db(self.db_name).table(table).insert(entry).run(c)
52-
c.close()
85+
c = self.__db_connect_retry(config.DB_RETRIES)
86+
res = None
87+
if c:
88+
res = r.db(self.db_name).table(table).insert(entry).run(c)
89+
c.close()
5390
return res
5491

5592
def db_select(self, table, db_filter):
56-
c = self.__db_connect_()
57-
if db_filter is None:
58-
res = r.db(self.db_name).table(table).run(c)
59-
else:
60-
res = r.db(self.db_name).table(table).filter(json.loads(db_filter)).run(c)
61-
c.close()
93+
c = self.__db_connect_retry(config.DB_RETRIES)
94+
res = None
95+
if c:
96+
if db_filter is None:
97+
res = r.db(self.db_name).table(table).run(c)
98+
else:
99+
res = r.db(self.db_name).table(table).filter(json.loads(db_filter)).run(c)
100+
c.close()
62101
return res
63102

64-
def db_update(self):
65-
pass
66-
67103
def db_remove(self, table, id):
68-
c = self.__db_connect_()
104+
c = self.__db_connect_retry(config.DB_RETRIES)
69105
res = None
70-
if id is not None:
71-
res = r.db(self.db_name).table(table).get(id).delete().run(c)
72-
c.close()
106+
if c:
107+
if id is not None:
108+
res = r.db(self.db_name).table(table).get(id).delete().run(c)
109+
c.close()
73110
return res
74-

launchers/codi-launcher.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from codi import codi
1818
from codi import codiDB
1919
from utils.globs import config
20+
import os
2021

2122
def register_codi_routes (app):
2223
app.add_url_rule('/codi/', 'api_list', codi.list_api)
@@ -30,8 +31,12 @@ def register_codi_routes (app):
3031

3132
if __name__ == '__main__':
3233
app = Flask(__name__)
33-
db = codiDB.CodiDB(config.CODI_DB)
34-
codi = codi.Codi(app, db)
35-
register_codi_routes(app)
34+
codi = codi.Codi(app)
3635
codi_args = codi.get_arg_parser()
36+
register_codi_routes(app)
3737
app.run(host=codi_args.ip, port=codi_args.port, debug=True)
38+
39+
if ("gunicorn" in os.environ.get("SERVER_SOFTWARE", "")):
40+
app = Flask(__name__)
41+
codi = codi.Codi(app)
42+
register_codi_routes(app)

launchers/gunicorn_config.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#
2+
# Copyright (C) 2016 Intel Corporation
3+
#
4+
# Author: Todor Minchev <[email protected]>
5+
#
6+
# This program is free software; you can redistribute it and/or modify it
7+
# under the terms and conditions of the GNU General Public License,
8+
# version 2, or (at your option) any later version, as published by
9+
# the Free Software Foundation.
10+
#
11+
# This program is distributed in the hope it will be useful, but WITHOUT
12+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14+
# more details.
15+
16+
from codi import codiDB
17+
18+
def db_init(server):
19+
codiDB.CodiDB("codi")
20+
21+
on_starting = db_init
22+

launchers/turff-launcher.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,9 @@
1818
from utils.globs import config
1919
import os
2020

21-
if __name__ == '__main__':
21+
def toolchain_reg(turff):
2222
response = Response()
23-
turff = turff.Turff()
2423
turff_args = turff.get_arg_parser()
25-
2624
for jfile in os.listdir(turff_args.jsonRoot + "/"):
2725
if jfile.endswith(".json"):
2826
jdata = turff.load_json(turff_args.jsonRoot +
@@ -38,3 +36,13 @@
3836
print("Registration successful : " + jfile)
3937
else:
4038
print("Registration failed : " + jfile)
39+
return False
40+
return True
41+
42+
if __name__ == '__main__':
43+
turff = turff.Turff()
44+
retries = turff.get_arg_parser().retries
45+
success = False
46+
while (bool(retries) & (not success)):
47+
success=toolchain_reg(turff)
48+
retries -= 1

tests.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
''' crops-py test runner '''
2+
3+
import unittest
4+
import sys
5+
6+
def run_unittests():
7+
''' Execute Unit Tests '''
8+
tests = unittest.TestLoader().discover('tests/unit')
9+
result = unittest.TextTestRunner(verbosity=2).run(tests)
10+
return result.wasSuccessful()
11+
12+
def run_functional_tests():
13+
''' Execute Functional Tests '''
14+
tests = unittest.TestLoader().discover('tests/functional')
15+
result = unittest.TextTestRunner(verbosity=2).run(tests)
16+
return result.wasSuccessful()
17+
18+
if __name__ == '__main__':
19+
print("#" * 70)
20+
print("Test Runner: Unit tests")
21+
print("#" * 70)
22+
unit_results = run_unittests()
23+
24+
print("#" * 70)
25+
print("Test Runner: Functional tests")
26+
print("#" * 70)
27+
functional_results = run_functional_tests()
28+
29+
if unit_results and functional_results:
30+
sys.exit(0)
31+
else:
32+
sys.exit(1)

0 commit comments

Comments
 (0)