Add /recipes/list route and tests
Includes adding a lock for access to the git repo from the API.
This commit is contained in:
		
							parent
							
								
									90a8798f4c
								
							
						
					
					
						commit
						b6fb22133c
					
				| @ -14,15 +14,17 @@ | |||||||
| # You should have received a copy of the GNU General Public License | # You should have received a copy of the GNU General Public License | ||||||
| # along with this program.  If not, see <http://www.gnu.org/licenses/>. | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
| # | # | ||||||
| 
 | from collections import namedtuple | ||||||
| from flask import Flask | from flask import Flask | ||||||
| 
 | 
 | ||||||
| from pylorax.api.crossdomain import crossdomain | from pylorax.api.crossdomain import crossdomain | ||||||
| from pylorax.api.v0 import v0_api | from pylorax.api.v0 import v0_api | ||||||
| 
 | 
 | ||||||
|  | GitLock = namedtuple("GitLock", ["repo", "lock", "dir"]) | ||||||
|  | 
 | ||||||
| server = Flask(__name__) | server = Flask(__name__) | ||||||
| 
 | 
 | ||||||
| __all__ = ["server"] | __all__ = ["server", "GitLock"] | ||||||
| 
 | 
 | ||||||
| @server.route('/') | @server.route('/') | ||||||
| @crossdomain(origin="*") | @crossdomain(origin="*") | ||||||
|  | |||||||
| @ -14,13 +14,14 @@ | |||||||
| # You should have received a copy of the GNU General Public License | # You should have received a copy of the GNU General Public License | ||||||
| # along with this program.  If not, see <http://www.gnu.org/licenses/>. | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
| # | # | ||||||
| from flask import jsonify | from flask import jsonify, request | ||||||
| 
 | 
 | ||||||
| # Use pykickstart to calculate disk image size | # Use pykickstart to calculate disk image size | ||||||
| from pykickstart.parser import KickstartParser | from pykickstart.parser import KickstartParser | ||||||
| from pykickstart.version import makeVersion, RHEL7 | from pykickstart.version import makeVersion, RHEL7 | ||||||
| 
 | 
 | ||||||
| from pylorax.api.crossdomain import crossdomain | from pylorax.api.crossdomain import crossdomain | ||||||
|  | from pylorax.api.recipes import list_branch_files | ||||||
| from pylorax.creator import DRACUT_DEFAULT, mount_boot_part_over_root | from pylorax.creator import DRACUT_DEFAULT, mount_boot_part_over_root | ||||||
| from pylorax.creator import make_appliance, make_image, make_livecd, make_live_images | from pylorax.creator import make_appliance, make_image, make_livecd, make_live_images | ||||||
| from pylorax.creator import make_runtime, make_squashfs | from pylorax.creator import make_runtime, make_squashfs | ||||||
| @ -29,6 +30,8 @@ from pylorax.imgutils import Mount, PartitionMount, umount | |||||||
| from pylorax.installer import InstallError | from pylorax.installer import InstallError | ||||||
| from pylorax.sysutils import joinpaths | from pylorax.sysutils import joinpaths | ||||||
| 
 | 
 | ||||||
|  | # The API functions don't actually get called by any code here | ||||||
|  | # pylint: disable=unused-variable | ||||||
| 
 | 
 | ||||||
| # no-virt mode doesn't need libvirt, so make it optional | # no-virt mode doesn't need libvirt, so make it optional | ||||||
| try: | try: | ||||||
| @ -42,3 +45,18 @@ def v0_api(api): | |||||||
|     @crossdomain(origin="*") |     @crossdomain(origin="*") | ||||||
|     def v0_status(): |     def v0_status(): | ||||||
|         return jsonify(build="devel", api="0", db_version="0", schema_version="0", db_supported=False) |         return jsonify(build="devel", api="0", db_version="0", schema_version="0", db_supported=False) | ||||||
|  | 
 | ||||||
|  |     @api.route("/api/v0/recipes/list") | ||||||
|  |     @crossdomain(origin="*") | ||||||
|  |     def v0_recipes_list(): | ||||||
|  |         """List the available recipes on a branch.""" | ||||||
|  |         try: | ||||||
|  |             limit = int(request.args.get("limit", "20")) | ||||||
|  |             offset = int(request.args.get("offset", "0")) | ||||||
|  |         except ValueError: | ||||||
|  |             # TODO return an error | ||||||
|  |             pass | ||||||
|  | 
 | ||||||
|  |         with api.config["GITLOCK"].lock: | ||||||
|  |             recipes = map(lambda f: f[:-5], list_branch_files(api.config["GITLOCK"].repo, "master")) | ||||||
|  |         return jsonify(recipes=recipes, limit=limit, offset=offset, total=len(recipes)) | ||||||
|  | |||||||
| @ -25,10 +25,12 @@ pylorax_log = logging.getLogger("pylorax") | |||||||
| import argparse | import argparse | ||||||
| import os | import os | ||||||
| import sys | import sys | ||||||
|  | from threading import Lock | ||||||
| from gevent.wsgi import WSGIServer | from gevent.wsgi import WSGIServer | ||||||
| 
 | 
 | ||||||
| from pylorax import vernum | from pylorax import vernum | ||||||
| from pylorax.api.server import server | from pylorax.api.recipes import open_or_create_repo, commit_recipe_directory | ||||||
|  | from pylorax.api.server import server, GitLock | ||||||
| 
 | 
 | ||||||
| VERSION = "{0}-{1}".format(os.path.basename(sys.argv[0]), vernum) | VERSION = "{0}-{1}".format(os.path.basename(sys.argv[0]), vernum) | ||||||
| 
 | 
 | ||||||
| @ -105,6 +107,14 @@ if __name__ == '__main__': | |||||||
|         sys.exit(1) |         sys.exit(1) | ||||||
| 
 | 
 | ||||||
|     setup_logging(opts.logfile) |     setup_logging(opts.logfile) | ||||||
|  | 
 | ||||||
|  |     server.config["REPO_DIR"] = opts.RECIPES | ||||||
|  |     repo = open_or_create_repo(server.config["REPO_DIR"]) | ||||||
|  |     server.config["GITLOCK"] = GitLock(repo=repo, lock=Lock(), dir=opts.RECIPES) | ||||||
|  | 
 | ||||||
|  |     # Import example recipes | ||||||
|  |     commit_recipe_directory(server.config["GITLOCK"].repo, "master", opts.RECIPES) | ||||||
|  | 
 | ||||||
|     http_server = WSGIServer((opts.host, opts.port), server) |     http_server = WSGIServer((opts.host, opts.port), server) | ||||||
|     # The server writes directly to a file object, so point to our log directory |     # The server writes directly to a file object, so point to our log directory | ||||||
|     server_logfile = os.path.abspath(os.path.dirname(opts.logfile))+"/server.log" |     server_logfile = os.path.abspath(os.path.dirname(opts.logfile))+"/server.log" | ||||||
|  | |||||||
| @ -14,20 +14,30 @@ | |||||||
| # You should have received a copy of the GNU General Public License | # You should have received a copy of the GNU General Public License | ||||||
| # along with this program.  If not, see <http://www.gnu.org/licenses/>. | # along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
| # | # | ||||||
| import os | import tempfile | ||||||
|  | from threading import Lock | ||||||
| import unittest | import unittest | ||||||
| 
 | 
 | ||||||
| from flask import json | from flask import json | ||||||
| from pylorax.api.server import server | from pylorax.api.recipes import open_or_create_repo, commit_recipe_directory | ||||||
|  | from pylorax.api.server import server, GitLock | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ServerTestCase(unittest.TestCase): | class ServerTestCase(unittest.TestCase): | ||||||
| 
 | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def setUpClass(self): |     def setUpClass(self): | ||||||
|  |         repo_dir = tempfile.mkdtemp(prefix="lorax.test.repo.") | ||||||
|  |         server.config["REPO_DIR"] = repo_dir | ||||||
|  |         repo = open_or_create_repo(server.config["REPO_DIR"]) | ||||||
|  |         server.config["GITLOCK"] = GitLock(repo=repo, lock=Lock(), dir=repo_dir) | ||||||
|  | 
 | ||||||
|         server.config['TESTING'] = True |         server.config['TESTING'] = True | ||||||
|         self.server = server.test_client() |         self.server = server.test_client() | ||||||
| 
 | 
 | ||||||
|  |         # Import the example recipes | ||||||
|  |         commit_recipe_directory(server.config["GITLOCK"].repo, "master", "tests/pylorax/recipes/") | ||||||
|  | 
 | ||||||
|     @classmethod |     @classmethod | ||||||
|     def tearDownClass(self): |     def tearDownClass(self): | ||||||
|         pass |         pass | ||||||
| @ -38,3 +48,11 @@ class ServerTestCase(unittest.TestCase): | |||||||
|         resp = self.server.get("/api/v0/status") |         resp = self.server.get("/api/v0/status") | ||||||
|         data = json.loads(resp.data) |         data = json.loads(resp.data) | ||||||
|         self.assertEqual(data, status_dict) |         self.assertEqual(data, status_dict) | ||||||
|  | 
 | ||||||
|  |     def test_recipes_list(self): | ||||||
|  |         """Test the /api/v0/recipes/list route""" | ||||||
|  |         list_dict = {"recipes":["atlas", "development", "glusterfs", "http-server", "jboss", "kubernetes"], | ||||||
|  |                      "limit":20, "offset":0, "total":6} | ||||||
|  |         resp = self.server.get("/api/v0/recipes/list") | ||||||
|  |         data = json.loads(resp.data) | ||||||
|  |         self.assertEqual(data, list_dict) | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user