forked from 3wordchant/capsul-flask
hub allocate capsul IP addr when the create operation is being claimed
create.sh will now be passed two extra arguments from the web app: network_name and public_ipv4_address network_name will be virbr1 or virbr2 or whatever the network is called and public_ipv4_address will be an ipv4 from that network which is not currently being used
This commit is contained in:
@ -1,7 +1,9 @@
|
||||
import json
|
||||
import ipaddress
|
||||
|
||||
from flask import Blueprint
|
||||
from flask import current_app
|
||||
from flask import request
|
||||
from flask import request, make_response
|
||||
from werkzeug.exceptions import abort
|
||||
|
||||
from capsulflask.db import get_model
|
||||
@ -46,15 +48,109 @@ def heartbeat(host_id):
|
||||
@bp.route("/claim-operation/<int:operation_id>/<string:host_id>", methods=("POST",))
|
||||
def claim_operation(operation_id: int, host_id: str):
|
||||
if authorized_for_host(host_id):
|
||||
exists = get_model().host_operation_exists(operation_id, host_id)
|
||||
if not exists:
|
||||
return abort(404, "host operation not found")
|
||||
payload_json = get_model().get_payload_json_from_host_operation(operation_id, host_id)
|
||||
if payload_json is None:
|
||||
error_message = f"{host_id} can't claim operation {operation_id} because host_operation row not found"
|
||||
current_app.logger.error(error_message)
|
||||
return abort(404, error_message)
|
||||
|
||||
can_claim_handlers = {
|
||||
"create": can_claim_create,
|
||||
}
|
||||
error_message = ""
|
||||
payload = None
|
||||
payload_is_dict = False
|
||||
payload_has_type = False
|
||||
payload_has_valid_type = False
|
||||
try:
|
||||
payload = json.loads(payload_json)
|
||||
payload_is_dict = isinstance(payload, dict)
|
||||
payload_has_type = payload_is_dict and 'type' in payload
|
||||
payload_has_valid_type = payload_has_type and payload['type'] in can_claim_handlers
|
||||
|
||||
if not payload_is_dict:
|
||||
error_message = "invalid json: expected an object"
|
||||
elif not payload_has_type:
|
||||
error_message = "invalid json: 'type' field is required"
|
||||
elif not payload_has_valid_type:
|
||||
error_message = f"invalid json: expected type \"{payload['type']}\" to be one of [{', '.join(can_claim_handlers.keys())}]"
|
||||
except:
|
||||
error_message = "could not parse payload as json"
|
||||
|
||||
if error_message is not "":
|
||||
error_message = f"{host_id} can't claim operation {operation_id} because {error_message}"
|
||||
current_app.logger.error(error_message)
|
||||
return abort(400, error_message)
|
||||
|
||||
# we will only return this payload as json if claiming succeeds, so might as well do this now...
|
||||
payload['assignment_status'] = 'assigned'
|
||||
|
||||
# invoke the appropriate can_claim_handler for this operation type
|
||||
result_tuple = can_claim_handlers[payload['type']](payload, host_id)
|
||||
payload_json = result_tuple[0]
|
||||
error_message = result_tuple[1]
|
||||
|
||||
if error_message is not "":
|
||||
error_message = f"{host_id} can't claim operation {operation_id} because {error_message}"
|
||||
current_app.logger.error(error_message)
|
||||
return abort(400, error_message)
|
||||
|
||||
claimed = get_model().claim_operation(operation_id, host_id)
|
||||
if claimed:
|
||||
return "ok"
|
||||
get_model().update_operation(operation_id, payload_json)
|
||||
|
||||
response = make_response(payload_json)
|
||||
response.header.set("Content-Type", "application/json")
|
||||
|
||||
return response
|
||||
else:
|
||||
return abort(409, "operation was already assigned to another host")
|
||||
return abort(409, f"operation was already assigned to another host")
|
||||
else:
|
||||
current_app.logger.warning(f"/hub/claim-operation/{operation_id}/{host_id} returned 401: invalid token")
|
||||
return abort(401, "invalid host id or token")
|
||||
|
||||
def can_claim_create(payload, host_id) -> (str, str):
|
||||
|
||||
hosts = get_model().list_hosts_with_networks(host_id)
|
||||
|
||||
if host_id not in hosts:
|
||||
return "", f"the host \"{host_id}\" does not appear to have any networks."
|
||||
|
||||
networks = hosts[host_id].networks
|
||||
|
||||
vms_by_host_and_network = get_model().non_deleted_vms_by_host_and_network(host_id)
|
||||
|
||||
vms_by_network = dict()
|
||||
if host_id in vms_by_host_and_network:
|
||||
vms_by_network = vms_by_host_and_network[host_id]
|
||||
|
||||
allocated_ipv4_address = None
|
||||
allocated_network_name = None
|
||||
for network in networks:
|
||||
vms = []
|
||||
if network["network_name"] in vms_by_network:
|
||||
vms = vms_by_network[network["network_name"]]
|
||||
|
||||
claimed_ipv4s = dict()
|
||||
for vm in vms:
|
||||
claimed_ipv4s[vm['public_ipv4']] = True
|
||||
|
||||
ipv4_network = ipaddress.ip_network(network["public_ipv4_cidr_block"], False)
|
||||
i = 0
|
||||
for ipv4_address in ipv4_network:
|
||||
i += 1
|
||||
if i > 2 and str(ipv4_address) not in claimed_ipv4s:
|
||||
allocated_ipv4_address = str(ipv4_address)
|
||||
break
|
||||
|
||||
if allocated_ipv4_address is not None:
|
||||
allocated_network_name = network["network_name"]
|
||||
break
|
||||
|
||||
if allocated_network_name is None or allocated_ipv4_address is None:
|
||||
return "", f"host \"{host_id}\" does not have any avaliable IP addresses on any of its networks."
|
||||
|
||||
payload["network_name"] = allocated_network_name
|
||||
payload["public_ipv4_address"] = allocated_ipv4_address
|
||||
|
||||
return json.dumps(payload), ""
|
Reference in New Issue
Block a user