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.
This commit is contained in:
		
							parent
							
								
									2692e8138c
								
							
						
					
					
						commit
						3a453eaad7
					
				| @ -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 | ||||
|  | ||||
| @ -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/<uuids>", | ||||
|     "/compose/info/<uuid>", | ||||
|     "/projects/source/info/<source_names>", | ||||
|     "/projects/source/new", | ||||
| ] | ||||
|  | ||||
| @ -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/<uuids>") | ||||
| @ -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": | ||||
|  | ||||
| @ -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/<uuids>") | ||||
| @checkparams([("uuids", "", "no UUIDs given")]) | ||||
| def v1_compose_status(uuids): | ||||
|     """Return the status of the listed uuids | ||||
| 
 | ||||
|     **/api/v1/compose/status/<uuids>[?blueprint=<blueprint_name>&status=<compose_status>&type=<compose_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/<uuid>") | ||||
| @checkparams([("uuid", "", "no UUID given")]) | ||||
| def v1_compose_info(uuid): | ||||
|     """Return detailed info about a compose | ||||
| 
 | ||||
|     **/api/v1/compose/info/<uuid>** | ||||
| 
 | ||||
|       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/<compose_uuid>", methods=["POST"]) | ||||
|  | ||||
| @ -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/<uuid> 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/<uuid> 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") | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user