fleshing out spoke API
This commit is contained in:
parent
0a379b74ad
commit
ad082eb0f9
@ -241,6 +241,7 @@ class CapsulFlaskHub(HubInterface):
|
|||||||
operation_id = op[0]
|
operation_id = op[0]
|
||||||
results = op[1]
|
results = op[1]
|
||||||
number_of_assigned = 0
|
number_of_assigned = 0
|
||||||
|
error_message = ""
|
||||||
assigned_hosts = []
|
assigned_hosts = []
|
||||||
for i in range(len(results)):
|
for i in range(len(results)):
|
||||||
host = online_hosts[i]
|
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":
|
if isinstance(result_body, dict) and 'assignment_status' in result_body and result_body['assignment_status'] == "assigned":
|
||||||
number_of_assigned += 1
|
number_of_assigned += 1
|
||||||
assigned_hosts.append(host.id)
|
assigned_hosts.append(host.id)
|
||||||
|
if isinstance(result_body, dict) and 'error_message' in result_body:
|
||||||
|
error_message = result_body['error_message']
|
||||||
except:
|
except:
|
||||||
# no need to do anything here since if it cant be parsed then generic_operation will handle it.
|
# no need to do anything here since if it cant be parsed then generic_operation will handle it.
|
||||||
pass
|
pass
|
||||||
@ -257,7 +260,8 @@ class CapsulFlaskHub(HubInterface):
|
|||||||
if number_of_assigned != 1:
|
if number_of_assigned != 1:
|
||||||
assigned_hosts_string = ", ".join(assigned_hosts)
|
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})")
|
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):
|
def destroy(self, email: str, id: str):
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
|
||||||
|
import sys
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from flask import request
|
from flask import request
|
||||||
|
from flask.json import jsonify
|
||||||
from werkzeug.exceptions import abort
|
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")
|
bp = Blueprint("spoke", __name__, url_prefix="/spoke")
|
||||||
|
|
||||||
@ -20,6 +22,106 @@ def heartbeat():
|
|||||||
# succeed or fail based on whether the request succeeds or fails.
|
# succeed or fail based on whether the request succeeds or fails.
|
||||||
pass
|
pass
|
||||||
else:
|
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")
|
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}
|
{completedProcess.stderr}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def destroy(self, email: str, id: str) -> str:
|
def destroy(self, email: str, id: str):
|
||||||
validate_capsul_id(id)
|
validate_capsul_id(id)
|
||||||
completedProcess = run([join(current_app.root_path, 'shell_scripts/destroy.sh'), id], capture_output=True)
|
completedProcess = run([join(current_app.root_path, 'shell_scripts/destroy.sh'), id], capture_output=True)
|
||||||
self.validate_completed_process(completedProcess, email)
|
self.validate_completed_process(completedProcess, email)
|
||||||
lines = completedProcess.stdout.splitlines()
|
lines = completedProcess.stdout.splitlines()
|
||||||
status = lines[len(lines)-1].decode("utf-8")
|
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}
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user