fixing get inconsistency and adding vm_state_command
This commit is contained in:
		| @ -30,11 +30,11 @@ def makeCapsulId(): | ||||
| def double_check_capsul_address(id, ipv4, get_ssh_host_keys): | ||||
|   try: | ||||
|     result = current_app.config["HUB_MODEL"].get(id, get_ssh_host_keys) | ||||
|     if result != None and result.ipv4 != ipv4: | ||||
|     if result != None and result.ipv4 != None and result.ipv4 != ipv4: | ||||
|       ipv4 = result.ipv4 | ||||
|       get_model().update_vm_ip(email=session["account"], id=id, ipv4=result.ipv4) | ||||
|  | ||||
|     if result != None and get_ssh_host_keys: | ||||
|     if result != None and result.ipv4 != None and get_ssh_host_keys: | ||||
|       get_model().update_vm_ssh_host_keys(email=session["account"], id=id, ssh_host_keys=result.ssh_host_keys) | ||||
|   except: | ||||
|     current_app.logger.error(f""" | ||||
|  | ||||
| @ -45,6 +45,9 @@ class MockHub(VirtualizationInterface): | ||||
|   def destroy(self, email: str, id: str): | ||||
|     current_app.logger.info(f"mock destroy: {id} for {email}") | ||||
|  | ||||
|   def vm_state_command(self, email: str, id: str, command: str): | ||||
|     current_app.logger.info(f"mock {command}: {id} for {email}") | ||||
|  | ||||
|  | ||||
| class CapsulFlaskHub(VirtualizationInterface): | ||||
|  | ||||
| @ -236,3 +239,25 @@ class CapsulFlaskHub(VirtualizationInterface): | ||||
|  | ||||
|     if not result_status == "success": | ||||
|       raise ValueError(f"""failed to destroy vm "{id}" on host "{host.id}" for {email}: {result_json_string}""") | ||||
|  | ||||
|  | ||||
|   def vm_state_command(self, email: str, id: str, command: str): | ||||
|     validate_capsul_id(id) | ||||
|     result_status = None | ||||
|     host = get_model().host_of_capsul(id) | ||||
|     if host is not None: | ||||
|       payload = json.dumps(dict(type="vm_state_command", email=email, id=id, command=command)) | ||||
|       results = self.synchronous_operation([host], payload) | ||||
|       result_json_string = "<no response from host>" | ||||
|       for result in results: | ||||
|         try: | ||||
|           result_json_string = result.body | ||||
|           result_body = json.loads(result_json_string) | ||||
|           if isinstance(result_body, dict) and 'status' in result_body: | ||||
|             result_status = result_body['status'] | ||||
|         except:  | ||||
|           pass | ||||
|  | ||||
|     if not result_status == "success": | ||||
|       raise ValueError(f"""failed to {command} vm "{id}" on host "{host.id}" for {email}: {result_json_string}""") | ||||
|  | ||||
|  | ||||
| @ -39,6 +39,9 @@ class VirtualizationInterface: | ||||
|   def destroy(self, email: str, id: str): | ||||
|     pass | ||||
|  | ||||
|   def vm_state_command(self, email: str, id: str, command: str): | ||||
|     pass | ||||
|  | ||||
| def validate_capsul_id(id): | ||||
|   if not re.match(r"^(cvm|capsul)-[a-z0-9]{10}$", id): | ||||
|     raise ValueError(f"vm id \"{id}\" must match \"^capsul-[a-z0-9]{{10}}$\"") | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
|  | ||||
| #!/bin/sh | ||||
|  | ||||
| vmname="$1" | ||||
| @ -7,10 +8,13 @@ if echo "$vmname" | grep -vqE '^(cvm|capsul)-[a-z0-9]{10}$'; then | ||||
|   exit 1 | ||||
| fi | ||||
|  | ||||
| if ! virsh list --name --all | grep -qE "^$vmname$" ; then | ||||
| 	echo "Error: $vmname not found" | ||||
| 	exit 1 | ||||
| # this will let us know if the vm exists or not | ||||
| exists="false" | ||||
| if virsh domuuid "$vmname" | grep -vqE '^[\t\s\n]*$'; then | ||||
|   exists="true" | ||||
| fi | ||||
|  | ||||
| # this gets the ipv4 | ||||
| virsh domifaddr "$vmname" | awk '/vnet/ {print $4}' | cut -d'/' -f1 | ||||
| ipv4="$(virsh domifaddr "$vmname" | awk '/vnet/ {print $4}' | cut -d'/' -f1)" | ||||
|  | ||||
| echo "$exists $ipv4" | ||||
| @ -52,6 +52,7 @@ def operation_impl(operation_id: int): | ||||
|       "list_ids": handle_list_ids, | ||||
|       "create": handle_create, | ||||
|       "destroy": handle_destroy, | ||||
|       "vm_state_command": handle_vm_state_command, | ||||
|     } | ||||
|  | ||||
|     error_message = "" | ||||
| @ -90,11 +91,8 @@ def handle_get(operation_id, request_body): | ||||
|     return abort(400, f"bad request; id is required for get") | ||||
|  | ||||
|   vm = current_app.config['SPOKE_MODEL'].get(request_body['id'], request_body['get_ssh_host_keys']) | ||||
|   # TODO vm can be None when the capsul exists on this host but has no IP address yet | ||||
|   # when this happens it logs an "error reading assignment_status" | ||||
|   # To fix this we need to | ||||
|   # 1. modify shell script to return does it exist on this host + if it does whats its ip | ||||
|   # 2. if exists but no ip, return assigned with no ip  | ||||
|   if vm is None:  | ||||
|     return jsonify(dict(assignment_status="assigned")) | ||||
|    | ||||
|   return jsonify(dict(assignment_status="assigned", id=vm.id, host=vm.host, ipv4=vm.ipv4, ipv6=vm.ipv6, ssh_host_keys=vm.ssh_host_keys)) | ||||
|  | ||||
| @ -167,4 +165,26 @@ def handle_destroy(operation_id, request_body): | ||||
|     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")) | ||||
|  | ||||
|  | ||||
| def handle_vm_state_command(operation_id, request_body): | ||||
|  | ||||
|   required_properties = ['id', 'email', 'command'] | ||||
|   for required_property in required_properties: | ||||
|     if required_property not in request_body: | ||||
|       current_app.logger.error(f"/hosts/operation returned 400: {required_property} is required for vm_state_command") | ||||
|       return abort(400, f"bad request; {required_property} is required for vm_state_command") | ||||
|  | ||||
|   if request_body['command'] not in ["stop", "force-stop", "start", "restart"]: | ||||
|     current_app.logger.error(f"/hosts/operation returned 400: command ({request_body['command']}) must be one of stop, force-stop, start, or restart") | ||||
|     return abort(400, f"bad request; command ({request_body['command']}) must be one of stop, force-stop, start, or restart") | ||||
|  | ||||
|   try: | ||||
|     current_app.config['SPOKE_MODEL'].vm_state_command(id=request_body['id'], email=request_body['email'], command=request_body['command']) | ||||
|   except: | ||||
|     error_message = my_exec_info_message(sys.exc_info()) | ||||
|     current_app.logger.error(f"current_app.config['SPOKE_MODEL'].vm_state_command(id='{request_body['id']}', email='{request_body['email']}, command='{request_body['command']}') failed: {error_message}") | ||||
|     return jsonify(dict(assignment_status="assigned", status="error", error_message=error_message)) | ||||
|    | ||||
|   return jsonify(dict(assignment_status="assigned", status="success")) | ||||
| @ -41,6 +41,9 @@ class MockSpoke(VirtualizationInterface): | ||||
|   def destroy(self, email: str, id: str): | ||||
|     current_app.logger.info(f"mock destroy: {id} for {email}") | ||||
|  | ||||
|   def vm_state_command(self, email: str, id: str, command: str): | ||||
|     current_app.logger.info(f"mock {command}: {id} for {email}") | ||||
|  | ||||
| class ShellScriptSpoke(VirtualizationInterface): | ||||
|  | ||||
|   def validate_completed_process(self, completedProcess, email=None): | ||||
| @ -85,11 +88,20 @@ class ShellScriptSpoke(VirtualizationInterface): | ||||
|     lines = completedProcess.stdout.splitlines() | ||||
|     if len(lines) == 0: | ||||
|       return None | ||||
|        | ||||
|     ipaddr = lines[0].decode("utf-8") | ||||
|      | ||||
|     result_string = lines[0].decode("utf-8") | ||||
|  | ||||
|     fields = result_string.split(" ") | ||||
|     if fields[0] != "true": | ||||
|       return None | ||||
|  | ||||
|     if len(fields) < 2: | ||||
|       return VirtualMachine(id, current_app.config["SPOKE_HOST_ID"]) | ||||
|  | ||||
|     ipaddr = fields[1] | ||||
|  | ||||
|     if not re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", ipaddr): | ||||
|       return None | ||||
|       return VirtualMachine(id, current_app.config["SPOKE_HOST_ID"]) | ||||
|  | ||||
|     if get_ssh_host_keys: | ||||
|       try: | ||||
| @ -172,4 +184,13 @@ class ShellScriptSpoke(VirtualizationInterface): | ||||
|         {completedProcess.stderr} | ||||
|       """) | ||||
|  | ||||
|   def vm_state_command(self, email: str, id: str, command: str): | ||||
|     validate_capsul_id(id) | ||||
|     if command not in ["stop", "force-stop", "start", "restart"]: | ||||
|       raise ValueError(f"command ({command}) must be one of stop, force-stop, start, or restart") | ||||
|  | ||||
|     completedProcess = run([join(current_app.root_path, f"shell_scripts/{command}.sh"), id], capture_output=True) | ||||
|     self.validate_completed_process(completedProcess, email) | ||||
|     returned_string = completedProcess.stdout.decode("utf-8") | ||||
|     current_app.logger.info(f"{command} vm {id} for {email} returned: {returned_string}") | ||||
|      | ||||
|  | ||||
		Reference in New Issue
	
	Block a user