capsul-flask/capsulflask/spoke_api.py

132 lines
5.5 KiB
Python
Raw Normal View History

2021-01-03 20:44:56 +00:00
import sys
2021-01-03 01:07:43 +00:00
import aiohttp
from flask import Blueprint
from flask import current_app
from flask import request
2021-01-03 20:44:56 +00:00
from flask.json import jsonify
from werkzeug.exceptions import abort
2021-01-03 20:44:56 +00:00
from capsulflask.db import my_exec_info_message
2021-01-03 01:07:43 +00:00
bp = Blueprint("spoke", __name__, url_prefix="/spoke")
2021-01-03 01:07:43 +00:00
def authorized_as_hub(id):
auth_header_value = request.headers.get('Authorization').replace("Bearer ", "")
2021-01-03 01:07:43 +00:00
return auth_header_value == current_app.config["HUB_TOKEN"]
2021-01-03 01:07:43 +00:00
@bp.route("/heartbeat", methods=("POST"))
def heartbeat():
if authorized_as_hub(id):
# make request to hub-domain.com/hub/heartbeat/{current_app.config["SPOKE_HOST_ID"]}
# succeed or fail based on whether the request succeeds or fails.
pass
else:
2021-01-03 20:44:56 +00:00
current_app.logger.info(f"/hosts/heartbeat returned 401: invalid hub token")
2021-01-03 01:07:43 +00:00
return abort(401, "invalid hub token")
2021-01-03 20:44:56 +00:00
@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:
2021-01-03 21:19:29 +00:00
try:
return handlers[request_body['type']](request_body)
except:
error_message = my_exec_info_message(sys.exc_info())
current_app.logger.error(f"unhandled exception in {request_body['type']} handler: {error_message}")
return jsonify(dict(error_message=error_message))
2021-01-03 20:44:56 +00:00
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"))