fleshing out spoke API
This commit is contained in:
		| @ -241,6 +241,7 @@ class CapsulFlaskHub(HubInterface): | ||||
|     operation_id = op[0] | ||||
|     results = op[1] | ||||
|     number_of_assigned = 0 | ||||
|     error_message = "" | ||||
|     assigned_hosts = [] | ||||
|     for i in range(len(results)): | ||||
|       host = online_hosts[i] | ||||
| @ -250,6 +251,8 @@ class CapsulFlaskHub(HubInterface): | ||||
|         if isinstance(result_body, dict) and 'assignment_status' in result_body and result_body['assignment_status'] == "assigned": | ||||
|           number_of_assigned += 1 | ||||
|           assigned_hosts.append(host.id) | ||||
|         if isinstance(result_body, dict) and 'error_message' in result_body: | ||||
|           error_message = result_body['error_message'] | ||||
|       except: | ||||
|         # no need to do anything here since if it cant be parsed then generic_operation will handle it. | ||||
|         pass | ||||
| @ -257,7 +260,8 @@ class CapsulFlaskHub(HubInterface): | ||||
|     if number_of_assigned != 1: | ||||
|       assigned_hosts_string = ", ".join(assigned_hosts) | ||||
|       raise ValueError(f"expected create capsul operation {operation_id} to be assigned to one host, it was assigned to {number_of_assigned} ({assigned_hosts_string})") | ||||
|  | ||||
|     if error_message != "": | ||||
|       raise ValueError(f"create capsul operation {operation_id} on {assigned_hosts_string} failed with {error_message}") | ||||
|        | ||||
|  | ||||
|   def destroy(self, email: str, id: str): | ||||
|  | ||||
| @ -1,11 +1,13 @@ | ||||
|  | ||||
| import sys | ||||
| import aiohttp | ||||
| from flask import Blueprint | ||||
| from flask import current_app | ||||
| from flask import request | ||||
| from flask.json import jsonify | ||||
| from werkzeug.exceptions import abort | ||||
|  | ||||
| from capsulflask.db import get_model, my_exec_info_message | ||||
| from capsulflask.db import  my_exec_info_message | ||||
|  | ||||
| bp = Blueprint("spoke", __name__, url_prefix="/spoke") | ||||
|  | ||||
| @ -20,6 +22,106 @@ def heartbeat(): | ||||
|     # succeed or fail based on whether the request succeeds or fails. | ||||
|     pass | ||||
|   else: | ||||
|     current_app.logger.info(f"/hosts/heartbeat returned 401: invalid token") | ||||
|     current_app.logger.info(f"/hosts/heartbeat returned 401: invalid hub token") | ||||
|     return abort(401, "invalid hub token") | ||||
|  | ||||
| @bp.route("/operation", methods=("POST")) | ||||
| def operation(): | ||||
|   if authorized_as_hub(id): | ||||
|     request_body = request.json() | ||||
|     handlers = { | ||||
|       "capacity_avaliable": handle_capacity_avaliable, | ||||
|       "get": handle_get, | ||||
|       "list_ids": handle_list_ids, | ||||
|       "create": handle_create, | ||||
|       "destroy": handle_destroy, | ||||
|     } | ||||
|  | ||||
|     error_message = "" | ||||
|     types_csv = ", ".join(handlers.keys()) | ||||
|     if isinstance(request_body, dict) and 'type' in request_body: | ||||
|       if request_body['type'] in handlers: | ||||
|         return handlers[request_body['type']](request_body) | ||||
|       else: | ||||
|         error_message = f"'type' must be one of {types_csv}" | ||||
|     else: | ||||
|       error_message = "'type' json property is required" | ||||
|  | ||||
|     if error_message != "": | ||||
|       current_app.logger.info(f"/hosts/operation returned 400: {error_message}") | ||||
|       return abort(400, f"bad request; {error_message}") | ||||
|   else: | ||||
|     current_app.logger.info(f"/hosts/operation returned 401: invalid hub token") | ||||
|     return abort(401, "invalid hub token") | ||||
|  | ||||
| def handle_capacity_avaliable(request_body): | ||||
|   if 'additional_ram_bytes' not in request_body: | ||||
|     current_app.logger.info(f"/hosts/operation returned 400: additional_ram_bytes is required for capacity_avaliable") | ||||
|     return abort(400, f"bad request; additional_ram_bytes is required for capacity_avaliable") | ||||
|  | ||||
|   has_capacity = current_app.config['SPOKE_MODEL'].capacity_avaliable(request_body['additional_ram_bytes']) | ||||
|   return jsonify(dict(assignment_status="assigned", capacity_avaliable=has_capacity)) | ||||
|  | ||||
| def handle_get(request_body): | ||||
|   if 'id' not in request_body: | ||||
|     current_app.logger.info(f"/hosts/operation returned 400: id is required for get") | ||||
|     return abort(400, f"bad request; id is required for get") | ||||
|  | ||||
|   vm = current_app.config['SPOKE_MODEL'].get(request_body['id']) | ||||
|    | ||||
|   return jsonify(dict(assignment_status="assigned", id=vm.id, host=vm.host, ipv4=vm.ipv4, ipv6=vm.ipv6)) | ||||
|  | ||||
| def handle_list_ids(request_body): | ||||
|   return jsonify(dict(assignment_status="assigned", ids=current_app.config['SPOKE_MODEL'].list_ids())) | ||||
|  | ||||
| def handle_create(request_body): | ||||
|   parameters = ["operation_id", "email", "id", "template_image_file_name", "vcpus", "memory_mb", "ssh_public_keys"] | ||||
|   error_message = "" | ||||
|   for parameter in parameters: | ||||
|     if parameter not in request_body: | ||||
|       error_message = f"{error_message}\n{parameter} is required for create" | ||||
|    | ||||
|   if error_message != "": | ||||
|     current_app.logger.info(f"/hosts/operation returned 400: {error_message}") | ||||
|     return abort(400, f"bad request; {error_message}") | ||||
|  | ||||
|   # try to aquire operation_id | ||||
|   assignment_status = "assigned" | ||||
|  | ||||
|   if assignment_status == "assigned": | ||||
|     try: | ||||
|       current_app.config['SPOKE_MODEL'].create( | ||||
|         email=request_body['email'], | ||||
|         id=request_body['id'], | ||||
|         template_image_file_name=request_body['template_image_file_name'], | ||||
|         vcpus=request_body['vcpus'], | ||||
|         memory_mb=request_body['memory_mb'], | ||||
|         ssh_public_keys=request_body['ssh_public_keys'], | ||||
|       ) | ||||
|     except: | ||||
|       error_message = my_exec_info_message(sys.exc_info()) | ||||
|       params = f"email='{request_body['email']}', id='{request_body['id']}', " | ||||
|       params = f"{params}, template_image_file_name='{request_body['template_image_file_name']}', vcpus='{request_body['vcpus']}'" | ||||
|       params = f"{params}, memory_mb='{request_body['memory_mb']}', ssh_public_keys='{request_body['ssh_public_keys']}'" | ||||
|       current_app.logger.error(f"current_app.config['SPOKE_MODEL'].create({params}) failed: {error_message}") | ||||
|       return jsonify(dict(assignment_status=assignment_status, error_message=error_message)) | ||||
|  | ||||
|   return jsonify(dict(assignment_status=assignment_status)) | ||||
|  | ||||
| def handle_destroy(request_body): | ||||
|   if 'id' not in request_body: | ||||
|     current_app.logger.info(f"/hosts/operation returned 400: id is required for destroy") | ||||
|     return abort(400, f"bad request; id is required for destroy") | ||||
|  | ||||
|   if 'email' not in request_body: | ||||
|     current_app.logger.info(f"/hosts/operation returned 400: email is required for destroy") | ||||
|     return abort(400, f"bad request; email is required for destroy") | ||||
|  | ||||
|   try: | ||||
|     current_app.config['SPOKE_MODEL'].destroy(id=request_body['id'], email=request_body['email']) | ||||
|   except: | ||||
|     error_message = my_exec_info_message(sys.exc_info()) | ||||
|     current_app.logger.error(f"current_app.config['SPOKE_MODEL'].destroy(id='{request_body['id']}', email='{request_body['email']}') failed: {error_message}") | ||||
|     return jsonify(dict(assignment_status="assigned", status="error", error_message=error_message)) | ||||
|    | ||||
|   return jsonify(dict(assignment_status="assigned", status="success")) | ||||
| @ -156,12 +156,18 @@ class ShellScriptSpoke(SpokeInterface): | ||||
|         {completedProcess.stderr} | ||||
|       """) | ||||
|  | ||||
|   def destroy(self, email: str, id: str) -> str: | ||||
|   def destroy(self, email: str, id: str): | ||||
|     validate_capsul_id(id) | ||||
|     completedProcess = run([join(current_app.root_path, 'shell_scripts/destroy.sh'), id], capture_output=True) | ||||
|     self.validate_completed_process(completedProcess, email) | ||||
|     lines = completedProcess.stdout.splitlines() | ||||
|     status = lines[len(lines)-1].decode("utf-8") | ||||
|     return status | ||||
|     if not status == "success": | ||||
|       raise ValueError(f"""failed to destroy vm {id} for {email} on {current_app.config["SPOKE_HOST_ID"]}: | ||||
|         stdout: | ||||
|         {completedProcess.stdout} | ||||
|         stderr: | ||||
|         {completedProcess.stderr} | ||||
|       """) | ||||
|  | ||||
|      | ||||
|  | ||||
		Reference in New Issue
	
	Block a user