From 3a453eaad7da9a41b627e6ac8332a9d3a325e9fa Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Tue, 27 Aug 2019 14:13:01 -0700 Subject: [PATCH] Make sure V0 API doesn't return uploads information uploads should only be included in the V1 API routes, add `api` selection to the relevant helper functions and calls to them from v0.py Add new V1 routes with api=1 to include the uploads information in the results. Also add tests to ensure that V0 requests do not include uploads. --- src/pylorax/api/queue.py | 75 ++++++---- src/pylorax/api/server.py | 5 + src/pylorax/api/v0.py | 28 ++-- src/pylorax/api/v1.py | 256 ++++++++++++++++++++++++++++++++++- tests/pylorax/test_server.py | 58 ++++++++ 5 files changed, 373 insertions(+), 49 deletions(-) diff --git a/src/pylorax/api/queue.py b/src/pylorax/api/queue.py index c5b7c5af..5e11603e 100644 --- a/src/pylorax/api/queue.py +++ b/src/pylorax/api/queue.py @@ -305,11 +305,15 @@ def get_compose_type(results_dir): raise RuntimeError("Cannot find ks template for build %s" % os.path.basename(results_dir)) return t[0] -def compose_detail(cfg, results_dir): +def compose_detail(cfg, results_dir, api=1): """Return details about the build. + :param cfg: Configuration settings (required for api=1) + :type cfg: ComposerConfig :param results_dir: The directory containing the metadata and results for the build :type results_dir: str + :param api: Select which api version of the dict to return (default 1) + :type api: int :returns: A dictionary with details about the compose :rtype: dict :raises: IOError if it cannot read the directory, STATUS, or blueprint file. @@ -322,6 +326,7 @@ def compose_detail(cfg, results_dir): * blueprint - Blueprint name * version - Blueprint version * image_size - Size of the image, if finished. 0 otherwise. + * uploads - For API v1 details about uploading the image are included Various timestamps are also included in the dict. These are all Unix UTC timestamps. It is possible for these timestamps to not always exist, in which case they will be @@ -345,26 +350,31 @@ def compose_detail(cfg, results_dir): times = timestamp_dict(results_dir) - upload_uuids = uuid_get_uploads(cfg, build_id) - summaries = [upload.summary() for upload in get_uploads(cfg["upload"], upload_uuids)] + detail = {"id": build_id, + "queue_status": status, + "job_created": times.get(TS_CREATED), + "job_started": times.get(TS_STARTED), + "job_finished": times.get(TS_FINISHED), + "compose_type": compose_type, + "blueprint": blueprint["name"], + "version": blueprint["version"], + "image_size": image_size, + } - return {"id": build_id, - "queue_status": status, - "job_created": times.get(TS_CREATED), - "job_started": times.get(TS_STARTED), - "job_finished": times.get(TS_FINISHED), - "compose_type": compose_type, - "blueprint": blueprint["name"], - "version": blueprint["version"], - "image_size": image_size, - "uploads": summaries, - } + if api == 1: + # Get uploads for this build_id + upload_uuids = uuid_get_uploads(cfg, build_id) + summaries = [upload.summary() for upload in get_uploads(cfg["upload"], upload_uuids)] + detail["uploads"] = summaries + return detail -def queue_status(cfg): +def queue_status(cfg, api=1): """Return details about what is in the queue. :param cfg: Configuration settings :type cfg: ComposerConfig + :param api: Select which api version of the dict to return (default 1) + :type api: int :returns: A list of the new composes, and a list of the running composes :rtype: dict @@ -378,7 +388,7 @@ def queue_status(cfg): new_details = [] for n in new_queue: try: - d = compose_detail(cfg, n) + d = compose_detail(cfg, n, api) except IOError: continue new_details.append(d) @@ -386,7 +396,7 @@ def queue_status(cfg): run_details = [] for r in run_queue: try: - d = compose_detail(cfg, r) + d = compose_detail(cfg, r, api) except IOError: continue run_details.append(d) @@ -396,32 +406,36 @@ def queue_status(cfg): "run": run_details } -def uuid_status(cfg, uuid): +def uuid_status(cfg, uuid, api=1): """Return the details of a specific UUID compose :param cfg: Configuration settings :type cfg: ComposerConfig :param uuid: The UUID of the build :type uuid: str + :param api: Select which api version of the dict to return (default 1) + :type api: int :returns: Details about the build :rtype: dict or None - Returns the same dict as `compose_details()` + Returns the same dict as `compose_detail()` """ uuid_dir = joinpaths(cfg.get("composer", "lib_dir"), "results", uuid) try: - return compose_detail(cfg, uuid_dir) + return compose_detail(cfg, uuid_dir, api) except IOError: return None -def build_status(cfg, status_filter=None): +def build_status(cfg, status_filter=None, api=1): """Return the details of finished or failed builds :param cfg: Configuration settings :type cfg: ComposerConfig :param status_filter: What builds to return. None == all, "FINISHED", or "FAILED" :type status_filter: str - :returns: A list of the build details (from compose_details) + :param api: Select which api version of the dict to return (default 1) + :type api: int + :returns: A list of the build details (from compose_detail) :rtype: list of dicts This returns a list of build details for each of the matching builds on the @@ -441,7 +455,7 @@ def build_status(cfg, status_filter=None): try: status = open(joinpaths(build, "STATUS"), "r").read().strip() if status in status_filter: - results.append(compose_detail(cfg, build)) + results.append(compose_detail(cfg, build, api)) except IOError: pass return results @@ -573,7 +587,7 @@ def uuid_delete(cfg, uuid): shutil.rmtree(uuid_dir) return True -def uuid_info(cfg, uuid): +def uuid_info(cfg, uuid, api=1): """Return information about the composition :param cfg: Configuration settings @@ -614,17 +628,14 @@ def uuid_info(cfg, uuid): raise RuntimeError("Missing deps.toml for %s" % uuid) deps_dict = toml.loads(open(deps_path, "r").read()) - details = compose_detail(cfg, uuid_dir) + details = compose_detail(cfg, uuid_dir, api) commit_path = joinpaths(uuid_dir, "COMMIT") if not os.path.exists(commit_path): raise RuntimeError("Missing commit hash for %s" % uuid) commit_id = open(commit_path, "r").read().strip() - upload_uuids = uuid_get_uploads(cfg, uuid) - summaries = [upload.summary() for upload in get_uploads(cfg["upload"], upload_uuids)] - - return {"id": uuid, + info = {"id": uuid, "config": cfg_dict, "blueprint": frozen_dict, "commit": commit_id, @@ -632,8 +643,12 @@ def uuid_info(cfg, uuid): "compose_type": details["compose_type"], "queue_status": details["queue_status"], "image_size": details["image_size"], - "uploads": summaries, } + if api == 1: + upload_uuids = uuid_get_uploads(cfg, uuid) + summaries = [upload.summary() for upload in get_uploads(cfg["upload"], upload_uuids)] + info["uploads"] = summaries + return info def uuid_tar(cfg, uuid, metadata=False, image=False, logs=False): """Return a tar of the build data diff --git a/src/pylorax/api/server.py b/src/pylorax/api/server.py index 9f3f9b2e..1c42d94e 100644 --- a/src/pylorax/api/server.py +++ b/src/pylorax/api/server.py @@ -91,6 +91,11 @@ server.register_blueprint(v0_api, url_prefix="/api/v0/") # Use v0 routes by default skip_rules = [ "/compose", + "/compose/queue", + "/compose/finished", + "/compose/failed", + "/compose/status/", + "/compose/info/", "/projects/source/info/", "/projects/source/new", ] diff --git a/src/pylorax/api/v0.py b/src/pylorax/api/v0.py index 9d6825d2..7fb61aa4 100644 --- a/src/pylorax/api/v0.py +++ b/src/pylorax/api/v0.py @@ -1537,7 +1537,7 @@ def v0_compose_queue(): ] } """ - return jsonify(queue_status(api.config["COMPOSER_CFG"])) + return jsonify(queue_status(api.config["COMPOSER_CFG"], api=0)) @v0_api.route("/compose/finished") def v0_compose_finished(): @@ -1572,7 +1572,7 @@ def v0_compose_finished(): ] } """ - return jsonify(finished=build_status(api.config["COMPOSER_CFG"], "FINISHED")) + return jsonify(finished=build_status(api.config["COMPOSER_CFG"], "FINISHED", api=0)) @v0_api.route("/compose/failed") def v0_compose_failed(): @@ -1598,7 +1598,7 @@ def v0_compose_failed(): ] } """ - return jsonify(failed=build_status(api.config["COMPOSER_CFG"], "FAILED")) + return jsonify(failed=build_status(api.config["COMPOSER_CFG"], "FAILED", api=0)) @v0_api.route("/compose/status", defaults={'uuids': ""}) @v0_api.route("/compose/status/") @@ -1647,14 +1647,14 @@ def v0_compose_status(uuids): errors = [] if uuids.strip() == '*': - queue_status_dict = queue_status(api.config["COMPOSER_CFG"]) + queue_status_dict = queue_status(api.config["COMPOSER_CFG"], api=0) queue_new = queue_status_dict["new"] queue_running = queue_status_dict["run"] - candidates = queue_new + queue_running + build_status(api.config["COMPOSER_CFG"]) + candidates = queue_new + queue_running + build_status(api.config["COMPOSER_CFG"], api=0) else: candidates = [] for uuid in [n.strip().lower() for n in uuids.split(",")]: - details = uuid_status(api.config["COMPOSER_CFG"], uuid) + details = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if details is None: errors.append({"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}) else: @@ -1695,7 +1695,7 @@ def v0_compose_cancel(uuid): if VALID_API_STRING.match(uuid) is None: return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 @@ -1737,7 +1737,7 @@ def v0_compose_delete(uuids): results = [] errors = [] for uuid in [n.strip().lower() for n in uuids.split(",")]: - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: errors.append({"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}) elif status["queue_status"] not in ["FINISHED", "FAILED"]: @@ -1806,7 +1806,7 @@ def v0_compose_info(uuid): return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 try: - info = uuid_info(api.config["COMPOSER_CFG"], uuid) + info = uuid_info(api.config["COMPOSER_CFG"], uuid, api=0) except Exception as e: return jsonify(status=False, errors=[{"id": COMPOSE_ERROR, "msg": str(e)}]), 400 @@ -1835,7 +1835,7 @@ def v0_compose_metadata(uuid): if VALID_API_STRING.match(uuid) is None: return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 if status["queue_status"] not in ["FINISHED", "FAILED"]: @@ -1865,7 +1865,7 @@ def v0_compose_results(uuid): if VALID_API_STRING.match(uuid) is None: return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 elif status["queue_status"] not in ["FINISHED", "FAILED"]: @@ -1893,7 +1893,7 @@ def v0_compose_logs(uuid): if VALID_API_STRING.match(uuid) is None: return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 elif status["queue_status"] not in ["FINISHED", "FAILED"]: @@ -1918,7 +1918,7 @@ def v0_compose_image(uuid): if VALID_API_STRING.match(uuid) is None: return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 elif status["queue_status"] not in ["FINISHED", "FAILED"]: @@ -1975,7 +1975,7 @@ def v0_compose_log_tail(uuid): except ValueError as e: return jsonify(status=False, errors=[{"id": COMPOSE_ERROR, "msg": str(e)}]), 400 - status = uuid_status(api.config["COMPOSER_CFG"], uuid) + status = uuid_status(api.config["COMPOSER_CFG"], uuid, api=0) if status is None: return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 elif status["queue_status"] == "WAITING": diff --git a/src/pylorax/api/v1.py b/src/pylorax/api/v1.py index 8c84482d..9b080500 100644 --- a/src/pylorax/api/v1.py +++ b/src/pylorax/api/v1.py @@ -29,8 +29,10 @@ from pylorax.api.checkparams import checkparams from pylorax.api.compose import start_build from pylorax.api.errors import BAD_COMPOSE_TYPE, BUILD_FAILED, INVALID_CHARS, MISSING_POST, PROJECTS_ERROR from pylorax.api.errors import SYSTEM_SOURCE, UNKNOWN_BLUEPRINT, UNKNOWN_SOURCE, UNKNOWN_UUID, UPLOAD_ERROR +from pylorax.api.errors import COMPOSE_ERROR from pylorax.api.flask_blueprint import BlueprintSkip -from pylorax.api.queue import uuid_status, uuid_schedule_upload, uuid_remove_upload +from pylorax.api.queue import queue_status, build_status, uuid_status, uuid_schedule_upload, uuid_remove_upload +from pylorax.api.queue import uuid_info from pylorax.api.projects import get_repo_sources, repo_to_source from pylorax.api.projects import new_repo_source from pylorax.api.regexes import VALID_API_STRING, VALID_BLUEPRINT_NAME @@ -115,7 +117,7 @@ def v1_projects_source_info(source_ids): def v1_projects_source_new(): """Add a new package source. Or change an existing one - **POST /api/v0/projects/source/new** + **POST /api/v1/projects/source/new** Add (or change) a source for use when depsolving blueprints and composing images. @@ -184,7 +186,7 @@ def v1_compose_start(): compose_type - The type of output to create, from /compose/types branch - Optional, defaults to master, selects the git branch to use for the blueprint. - **POST /api/v0/compose** + **POST /api/v1/compose** Start a compose. The content type should be 'application/json' and the body of the POST should look like this. The "upload" object is optional. @@ -212,7 +214,7 @@ def v1_compose_start(): } Pass it the name of the blueprint, the type of output (from - '/api/v0/compose/types'), and the blueprint branch to use. 'branch' is + '/api/v1/compose/types'), and the blueprint branch to use. 'branch' is optional and will default to master. It will create a new build and add it to the queue. It returns the build uuid and a status if it succeeds. If an "upload" is given, it will schedule an upload to run when the build @@ -222,6 +224,7 @@ def v1_compose_start(): { "build_id": "e6fa6db4-9c81-4b70-870f-a697ca405cdf", + "upload_uuid": "572eb0d0-5348-4600-9666-14526ba628bb", "status": true } """ @@ -294,8 +297,251 @@ def v1_compose_start(): image_name, settings ) + else: + upload_uuid = "" - return jsonify(status=True, build_id=build_id) + return jsonify(status=True, build_id=build_id, upload_id=upload_uuid) + +@v1_api.route("/compose/queue") +def v1_compose_queue(): + """Return the status of the new and running queues + + **/api/v1/compose/queue** + + Return the status of the build queue. It includes information about the builds waiting, + and the build that is running. + + Example:: + + { + "new": [ + { + "id": "45502a6d-06e8-48a5-a215-2b4174b3614b", + "blueprint": "glusterfs", + "queue_status": "WAITING", + "job_created": 1517362647.4570868, + "version": "0.0.6" + }, + { + "id": "6d292bd0-bec7-4825-8d7d-41ef9c3e4b73", + "blueprint": "kubernetes", + "queue_status": "WAITING", + "job_created": 1517362659.0034983, + "version": "0.0.1" + } + ], + "run": [ + { + "id": "745712b2-96db-44c0-8014-fe925c35e795", + "blueprint": "glusterfs", + "queue_status": "RUNNING", + "job_created": 1517362633.7965999, + "job_started": 1517362633.8001345, + "version": "0.0.6" + } + ] + } + """ + return jsonify(queue_status(api.config["COMPOSER_CFG"], api=1)) + +@v1_api.route("/compose/finished") +def v1_compose_finished(): + """Return the list of finished composes + + **/api/v1/compose/finished** + + Return the details on all of the finished composes on the system. + + Example:: + + { + "finished": [ + { + "id": "70b84195-9817-4b8a-af92-45e380f39894", + "blueprint": "glusterfs", + "queue_status": "FINISHED", + "job_created": 1517351003.8210032, + "job_started": 1517351003.8230415, + "job_finished": 1517359234.1003145, + "version": "0.0.6" + }, + { + "id": "e695affd-397f-4af9-9022-add2636e7459", + "blueprint": "glusterfs", + "queue_status": "FINISHED", + "job_created": 1517362289.7193348, + "job_started": 1517362289.9751132, + "job_finished": 1517363500.1234567, + "version": "0.0.6" + } + ] + } + """ + return jsonify(finished=build_status(api.config["COMPOSER_CFG"], "FINISHED", api=1)) + +@v1_api.route("/compose/failed") +def v1_compose_failed(): + """Return the list of failed composes + + **/api/v1/compose/failed** + + Return the details on all of the failed composes on the system. + + Example:: + + { + "failed": [ + { + "id": "8c8435ef-d6bd-4c68-9bf1-a2ef832e6b1a", + "blueprint": "http-server", + "queue_status": "FAILED", + "job_created": 1517523249.9301329, + "job_started": 1517523249.9314211, + "job_finished": 1517523255.5623411, + "version": "0.0.2" + } + ] + } + """ + return jsonify(failed=build_status(api.config["COMPOSER_CFG"], "FAILED", api=1)) + +@v1_api.route("/compose/status", defaults={'uuids': ""}) +@v1_api.route("/compose/status/") +@checkparams([("uuids", "", "no UUIDs given")]) +def v1_compose_status(uuids): + """Return the status of the listed uuids + + **/api/v1/compose/status/[?blueprint=&status=&type=]** + + Return the details for each of the comma-separated list of uuids. A uuid of '*' will return + details for all composes. + + Example:: + + { + "uuids": [ + { + "id": "8c8435ef-d6bd-4c68-9bf1-a2ef832e6b1a", + "blueprint": "http-server", + "queue_status": "FINISHED", + "job_created": 1517523644.2384307, + "job_started": 1517523644.2551234, + "job_finished": 1517523689.9864314, + "version": "0.0.2" + }, + { + "id": "45502a6d-06e8-48a5-a215-2b4174b3614b", + "blueprint": "glusterfs", + "queue_status": "FINISHED", + "job_created": 1517363442.188399, + "job_started": 1517363442.325324, + "job_finished": 1517363451.653621, + "version": "0.0.6" + } + ] + } + """ + if VALID_API_STRING.match(uuids) is None: + return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 + + blueprint = request.args.get("blueprint", None) + status = request.args.get("status", None) + compose_type = request.args.get("type", None) + + results = [] + errors = [] + + if uuids.strip() == '*': + queue_status_dict = queue_status(api.config["COMPOSER_CFG"], api=1) + queue_new = queue_status_dict["new"] + queue_running = queue_status_dict["run"] + candidates = queue_new + queue_running + build_status(api.config["COMPOSER_CFG"], api=1) + else: + candidates = [] + for uuid in [n.strip().lower() for n in uuids.split(",")]: + details = uuid_status(api.config["COMPOSER_CFG"], uuid, api=1) + if details is None: + errors.append({"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}) + else: + candidates.append(details) + + for details in candidates: + if blueprint is not None and details['blueprint'] != blueprint: + continue + + if status is not None and details['queue_status'] != status: + continue + + if compose_type is not None and details['compose_type'] != compose_type: + continue + + results.append(details) + + return jsonify(uuids=results, errors=errors) + +@v1_api.route("/compose/info", defaults={'uuid': ""}) +@v1_api.route("/compose/info/") +@checkparams([("uuid", "", "no UUID given")]) +def v1_compose_info(uuid): + """Return detailed info about a compose + + **/api/v1/compose/info/** + + Get detailed information about the compose. The returned JSON string will + contain the following information: + + * id - The uuid of the comoposition + * config - containing the configuration settings used to run Anaconda + * blueprint - The depsolved blueprint used to generate the kickstart + * commit - The (local) git commit hash for the blueprint used + * deps - The NEVRA of all of the dependencies used in the composition + * compose_type - The type of output generated (tar, iso, etc.) + * queue_status - The final status of the composition (FINISHED or FAILED) + + Example:: + + { + "commit": "7078e521a54b12eae31c3fd028680da7a0815a4d", + "compose_type": "tar", + "config": { + "anaconda_args": "", + "armplatform": "", + "compress_args": [], + "compression": "xz", + "image_name": "root.tar.xz", + ... + }, + "deps": { + "packages": [ + { + "arch": "x86_64", + "epoch": "0", + "name": "acl", + "release": "14.el7", + "version": "2.2.51" + } + ] + }, + "id": "c30b7d80-523b-4a23-ad52-61b799739ce8", + "queue_status": "FINISHED", + "blueprint": { + "description": "An example kubernetes master", + ... + } + } + """ + if VALID_API_STRING.match(uuid) is None: + return jsonify(status=False, errors=[{"id": INVALID_CHARS, "msg": "Invalid characters in API path"}]), 400 + + try: + info = uuid_info(api.config["COMPOSER_CFG"], uuid, api=1) + except Exception as e: + return jsonify(status=False, errors=[{"id": COMPOSE_ERROR, "msg": str(e)}]), 400 + + if info is None: + return jsonify(status=False, errors=[{"id": UNKNOWN_UUID, "msg": "%s is not a valid build uuid" % uuid}]), 400 + else: + return jsonify(**info) @v1_api.route("/compose/uploads/schedule", defaults={'compose_uuid': ""}, methods=["POST"]) @v1_api.route("/compose/uploads/schedule/", methods=["POST"]) diff --git a/tests/pylorax/test_server.py b/tests/pylorax/test_server.py index 91d6237a..b6d6073f 100644 --- a/tests/pylorax/test_server.py +++ b/tests/pylorax/test_server.py @@ -1252,6 +1252,10 @@ class ServerTestCase(unittest.TestCase): ids = [e["id"] for e in data["new"] + data["run"]] self.assertEqual(build_id in ids, True, "Failed to add build to the queue") + # V0 API should *not* have the uploads details in the results + uploads = any("uploads" in e for e in data["new"] + data["run"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + # Wait for it to start self.assertEqual(_wait_for_status(self, build_id, ["RUNNING"]), True, "Failed to start test compose") @@ -1270,6 +1274,11 @@ class ServerTestCase(unittest.TestCase): ids = [e["id"] for e in data["failed"]] self.assertEqual(build_id in ids, True, "Failed build not listed by /compose/failed") + # V0 API should *not* have the uploads details in the results + print(data) + uploads = any("uploads" in e for e in data["failed"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + # Test the /api/v0/compose/finished route resp = self.server.get("/api/v0/compose/finished") data = json.loads(resp.data) @@ -1283,6 +1292,11 @@ class ServerTestCase(unittest.TestCase): ids = [(e["id"], e["queue_status"]) for e in data["uuids"]] self.assertEqual((build_id, "FAILED") in ids, True, "Failed build not listed by /compose/status") + # V0 API should *not* have the uploads details in the results + print(data) + uploads = any("uploads" in e for e in data["uuids"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + # Test the /api/v0/compose/cancel/ route resp = self.server.post("/api/v0/compose?test=1", data=json.dumps(test_compose), @@ -1338,6 +1352,10 @@ class ServerTestCase(unittest.TestCase): ids = [e["id"] for e in data["new"] + data["run"]] self.assertEqual(build_id in ids, True, "Failed to add build to the queue") + # V0 API should *not* have the uploads details in the results + uploads = any("uploads" in e for e in data["new"] + data["run"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + # Wait for it to start self.assertEqual(_wait_for_status(self, build_id, ["RUNNING"]), True, "Failed to start test compose") @@ -1356,6 +1374,10 @@ class ServerTestCase(unittest.TestCase): ids = [e["id"] for e in data["finished"]] self.assertEqual(build_id in ids, True, "Finished build not listed by /compose/finished") + # V0 API should *not* have the uploads details in the results + uploads = any("uploads" in e for e in data["finished"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + # Test the /api/v0/compose/failed route resp = self.server.get("/api/v0/compose/failed") data = json.loads(resp.data) @@ -1369,6 +1391,10 @@ class ServerTestCase(unittest.TestCase): ids = [(e["id"], e["queue_status"]) for e in data["uuids"]] self.assertEqual((build_id, "FINISHED") in ids, True, "Finished build not listed by /compose/status") + # V0 API should *not* have the uploads details in the results + uploads = any("uploads" in e for e in data["uuids"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + # Test the /api/v0/compose/metadata/ route resp = self.server.get("/api/v0/compose/metadata/%s" % build_id) self.assertEqual(resp.status_code, 200) @@ -1439,6 +1465,10 @@ class ServerTestCase(unittest.TestCase): ids = [e["id"] for e in data["new"] + data["run"]] self.assertEqual(build_id_fail in ids, True, "Failed to add build to the queue") + # V0 API should *not* have the uploads details in the results + uploads = any("uploads" in e for e in data["new"] + data["run"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + # Wait for it to start self.assertEqual(_wait_for_status(self, build_id_fail, ["RUNNING"]), True, "Failed to start test compose") @@ -1461,6 +1491,10 @@ class ServerTestCase(unittest.TestCase): ids = [e["id"] for e in data["new"] + data["run"]] self.assertEqual(build_id_success in ids, True, "Failed to add build to the queue") + # V0 API should *not* have the uploads details in the results + uploads = any("uploads" in e for e in data["new"] + data["run"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + # Wait for it to start self.assertEqual(_wait_for_status(self, build_id_success, ["RUNNING"]), True, "Failed to start test compose") @@ -1475,6 +1509,10 @@ class ServerTestCase(unittest.TestCase): self.assertIn(build_id_success, ids, "Finished build not listed by /compose/status/*") self.assertIn(build_id_fail, ids, "Failed build not listed by /compose/status/*") + # V0 API should *not* have the uploads details in the results + uploads = any("uploads" in e for e in data["uuids"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + # Filter by name resp = self.server.get("/api/v0/compose/status/*?blueprint=%s" % test_compose_fail["blueprint_name"]) data = json.loads(resp.data) @@ -1483,6 +1521,10 @@ class ServerTestCase(unittest.TestCase): self.assertIn(build_id_fail, ids, "Failed build not listed by /compose/status blueprint filter") self.assertNotIn(build_id_success, ids, "Finished build listed by /compose/status blueprint filter") + # V0 API should *not* have the uploads details in the results + uploads = any("uploads" in e for e in data["uuids"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + # Filter by type resp = self.server.get("/api/v0/compose/status/*?type=tar") data = json.loads(resp.data) @@ -1491,6 +1533,10 @@ class ServerTestCase(unittest.TestCase): self.assertIn(build_id_fail, ids, "Failed build not listed by /compose/status type filter") self.assertIn(build_id_success, ids, "Finished build not listed by /compose/status type filter") + # V0 API should *not* have the uploads details in the results + uploads = any("uploads" in e for e in data["uuids"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + resp = self.server.get("/api/v0/compose/status/*?type=snakes") data = json.loads(resp.data) self.assertNotEqual(data, None) @@ -1505,6 +1551,10 @@ class ServerTestCase(unittest.TestCase): self.assertIn(build_id_fail, ids, "Failed build not listed by /compose/status status filter") self.assertNotIn(build_id_success, "Finished build listed by /compose/status status filter") + # V0 API should *not* have the uploads details in the results + uploads = any("uploads" in e for e in data["uuids"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + def test_compose_14_kernel_append(self): """Test the /api/v0/compose with kernel append customization""" test_compose = {"blueprint_name": "example-append", @@ -1527,6 +1577,10 @@ class ServerTestCase(unittest.TestCase): ids = [e["id"] for e in data["new"] + data["run"]] self.assertEqual(build_id in ids, True, "Failed to add build to the queue") + # V0 API should *not* have the uploads details in the results + uploads = any("uploads" in e for e in data["new"] + data["run"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + # Wait for it to start self.assertEqual(_wait_for_status(self, build_id, ["RUNNING"]), True, "Failed to start test compose") @@ -2177,6 +2231,10 @@ class GitRPMBlueprintTestCase(unittest.TestCase): ids = [e["id"] for e in data["new"] + data["run"]] self.assertEqual(build_id in ids, True, "Failed to add build to the queue") + # V0 API should *not* have the uploads details in the results + uploads = any("uploads" in e for e in data["new"] + data["run"]) + self.assertFalse(uploads, "V0 API should not include 'uploads' field") + # Wait for it to start self.assertEqual(_wait_for_status(self, build_id, ["RUNNING"]), True, "Failed to start test compose")