When a VM starts it;s ports are set to active

Added network and volume details to streamlit
This commit is contained in:
2025-06-14 21:53:16 +09:30
parent 59965bdef5
commit 0bb177c1f4
4 changed files with 172 additions and 79 deletions
+78 -62
View File
@@ -678,99 +678,116 @@ def edit_VirtualMachine_workload(virtual_machine_id):
logger.error(error_message)
return jsonify({"success": False, "message": error_message}), 400
# Validate system_vm_id and worker_id as UUIDs
# Validate UUIDs
try:
worker_id = (data.get("worker_id"))
worker_id = data.get("worker_id")
virtual_machine_id = str(virtual_machine_id)
except (ValueError, TypeError) as e:
error_message = "Invalid UUID format for worker_id."
logger.error(f"{error_message} Error: {str(e)}")
return jsonify({"success": False, "message": error_message}), 400
logger.error("Invalid UUID format. Error: %s", str(e))
return jsonify({"success": False, "message": "Invalid UUID format."}), 400
# Validate virtual_machine_id as UUID
try:
virtual_machine_id = (virtual_machine_id)
except (ValueError, TypeError) as e:
error_message = "Invalid UUID format for virtual_machine_id."
logger.error(f"{error_message} Error: {str(e)}")
return jsonify({"success": False, "message": error_message}), 400
# Validate timestamp as ISO format
# Validate timestamp
try:
datetime.fromisoformat(data.get("timestamp"))
except (ValueError, TypeError) as e:
error_message = "Invalid ISO timestamp format."
logger.error(f"{error_message} Error: {str(e)}")
return jsonify({"success": False, "message": error_message}), 400
logger.error("Invalid ISO timestamp format. Error: %s", str(e))
return jsonify({"success": False, "message": "Invalid ISO timestamp format."}), 400
# Fetch the virtual machine from the database
try:
virtual_machine = Workload.query.filter_by(id=virtual_machine_id, workload_type="VirtualMachine").first_or_404()
except Exception as e:
error_message = f"Failed to fetch virtual machine with ID {virtual_machine_id}."
logger.error(f"{error_message} Error: {str(e)}")
return jsonify({"success": False, "message": error_message}), 404
logger.error("Failed to fetch VM. Error: %s", str(e))
return jsonify({"success": False, "message": "VM not found."}), 404
# Check if the VM is actually on the reported worker
if virtual_machine.workload_host_id != worker_id:
error_message = f"Workload host in DB for VM ID {virtual_machine_id} is {virtual_machine.workload_host_id}, but was reported from {worker_id}. Ignoring."
logger.error(error_message)
return jsonify({"success": False, "message": error_message}), 400
logger.error("Workload host mismatch for VM %s. Expected %s but got %s.",
virtual_machine_id, virtual_machine.workload_host_id, worker_id)
return jsonify({
"success": False,
"message": f"Reported worker ID doesn't match the DB value for VM {virtual_machine_id}."
}), 400
# Update the status of the virtual machine
try:
new_status = data.get('new_status')
logger.info(f"Updating VirtualMachine ID: {virtual_machine_id} to new status {new_status}")
logger.info("Updating VM %s to status %s", virtual_machine_id, new_status)
if new_status == "deleted":
# Mark all associated volumes as deleted
volume_mappings = VolumeWorkloadMapping.query.filter_by(
workload_id=virtual_machine.id
).all()
for mapping in volume_mappings:
mapping.volume.status = "deleted"
mapping.volume.soft_delete()
db.session.add(mapping.volume)
# Mark all associated network ports as deleted
network_ports = NetworkPort.query.filter_by(workload_id=virtual_machine.id).all()
for port in network_ports:
port.status = "deleted"
port.soft_delete()
db.session.add(port)
# Also soft delete the volume mappings
# Delete associated volumes
volume_mappings = VolumeWorkloadMapping.query.filter_by(workload_id=virtual_machine.id).all()
for mapping in volume_mappings:
if mapping.volume:
mapping.volume.status = "deleted"
mapping.volume.soft_delete()
db.session.add(mapping.volume)
mapping.soft_delete()
db.session.add(mapping)
virtual_machine.set_status(new_status)
if new_status == "deleted":
# Delete associated network ports
ports = NetworkPort.query.filter_by(workload_id=virtual_machine.id).all()
for port in ports:
port.status = "deleted"
port.soft_delete()
db.session.add(port)
virtual_machine.soft_delete()
elif new_status == "running":
# Activate attached network ports
ports = NetworkPort.query.filter_by(workload_id=virtual_machine.id, deleted=False).all()
for port in ports:
if port.status != "active":
port.status = "active"
db.session.add(port)
logger.info("Set port %s status to active", port.id)
virtual_machine.set_status(new_status)
db.session.add(virtual_machine)
db.session.commit()
return jsonify({"success": True, "message": "Status updated successfully."}), 200
except Exception as e:
error_message = f"Failed to update status for VirtualMachine ID: {virtual_machine_id}."
logger.error(f"{error_message} Error: {str(e)}")
db.session.rollback()
return jsonify({"success": False, "message": error_message}), 500
except Exception as e:
logger.error("Failed to update VM status. Error: %s", str(e))
db.session.rollback()
return jsonify({"success": False, "message": "Internal server error"}), 500
@api_bp.route('/workloads/virtual_machines/<workload_id>', methods=['GET'])
def get_VirtualMachine_workload(workload_id):
def get_virtual_machine_workload(workload_id):
try:
workload_uuid = (workload_id)
workload_uuid = str(workload_id)
except ValueError:
return jsonify({"error": "Invalid workload ID"}), 404
workload = Workload.query.filter(
Workload.id == workload_uuid,
Workload.workload_type == "VirtualMachine",
Workload.deleted == False
).first_or_404()
logger.info(f"{workload.workload_type} {workload.deleted}")
return jsonify(workload.to_json())
# Serialize the workload base info
response = workload.to_json()
# Add attached volumes (via VolumeWorkloadMapping)
volumes = []
for mapping in workload.volume_mappings:
if mapping.volume and not mapping.volume.deleted:
volumes.append(mapping.volume.to_json())
response["volumes"] = volumes
# Add attached network ports
ports = []
for port in workload.network_ports:
if port.deleted:
continue
port_json = port.to_json()
if port.network and not port.network.deleted:
port_json["network"] = port.network.to_json()
ports.append(port_json)
response["network_ports"] = ports
return jsonify(response)
@api_bp.route('/workloads/virtual_machines/<workload_id>', methods=['DELETE'])
def delete_VirtualMachine_workload(workload_id):
@@ -856,10 +873,9 @@ def get_network_ports_not_on_host(network_id: str, exclude_host_id: str):
return (
db.session.query(NetworkPort)
.join(WorkloadAlias, NetworkPort.workload_id == WorkloadAlias.id)
.join(WorkloadHostAlias, WorkloadAlias.workload_host_id == WorkloadHostAlias.id)
.filter(NetworkPort.network_id == network_id)
.filter(WorkloadAlias.workload_host_id != exclude_host_id)
.filter(WorkloadHostAlias.deleted == False)
.filter(WorkloadAlias.deleted == False)
.all()
)
Submodule sandbox/joke_container added at 4a8d0ac064
+91 -16
View File
@@ -285,10 +285,9 @@ def render_list():
else:
st.info("No VirtualMachine workloads found.")
def render_detail():
"""
Renders the details of a specific VM workload.
Renders the details of a specific VM workload, including attached volumes and network ports.
"""
VirtualMachine = st.session_state.client.get_virtual_machine(st.session_state.selected_resource['id'])
if not VirtualMachine:
@@ -311,15 +310,63 @@ def render_detail():
if VirtualMachine.get('updated_at'):
st.markdown(f"**Last Updated:** {format_timestamp(VirtualMachine['updated_at'])}")
st.markdown(f"**Status:** {VirtualMachine['status']}")
st.markdown(f"**Launch Parameters:** {VirtualMachine.get('launch_params', 'N/A')}")
# Edit VM Workload Form
st.markdown("**Launch Parameters:**")
launch_params = VirtualMachine.get("launch_params")
if not launch_params:
st.info("No launch parameters provided.")
else:
try:
decoded = json.loads(launch_params)
# Display known parameters
memory = decoded.pop("memory", None)
vcpu = decoded.pop("vcpu", None)
name = decoded.pop("name", None)
networks = decoded.pop("networks", [])
volumes = decoded.pop("volumes", [])
if name: st.markdown(f"- **Name:** `{name}`")
if memory: st.markdown(f"- **Memory:** `{memory} MB`")
if vcpu: st.markdown(f"- **vCPUs:** `{vcpu}`")
if networks:
st.markdown("- **Networks:**")
for net in networks:
st.markdown(f" - Network ID: `{net.get('id', 'unknown')}`")
else:
st.markdown("- **Networks:** None")
if volumes:
st.markdown("- **Volumes:**")
for vol in volumes:
st.markdown(f" - Volume: `{vol}`")
else:
st.markdown("- **Volumes:** None")
# Any other unknown keys
if decoded:
st.markdown("**Other Params:**")
for k, v in decoded.items():
st.markdown(f"- `{k}`: `{v}`")
except json.JSONDecodeError:
st.warning("Launch parameters are not valid JSON.")
# Edit VM Workload
with st.expander("Edit VM Workload"):
with st.form("edit_vm"):
new_status = st.selectbox("Status", ["running", "stopped", "pending", "failed-deleted", "pending-deleted", "pending-allocation", "failed-allocation", "pending-allocated"], index=["running", "stopped", "pending", "failed-deleted", "pending-deleted", "pending-allocation", "failed-allocation", "pending-allocated"].index(VirtualMachine['status']))
status_options = [
"running", "stopped", "pending",
"failed-deleted", "pending-deleted",
"pending-allocation", "failed-allocation",
"pending-allocated"
]
new_status = st.selectbox("Status", status_options, index=status_options.index(VirtualMachine['status']))
new_launch_params = st.text_input("Launch Parameters", value=VirtualMachine.get('launch_params', ''))
submit = st.form_submit_button("Update VM Workload")
if submit:
result = st.session_state.client.edit_virtual_machine(VirtualMachine['id'], {
"status": new_status,
@@ -327,24 +374,52 @@ def render_detail():
})
if result:
st.success("VM workload updated successfully!")
# Re-fetch the updated VM data to ensure consistency
updated_vm = st.session_state.client.get_virtual_machine(VirtualMachine['id'])
if updated_vm:
st.session_state.selected_resource = updated_vm
else: # Handle case where VM might not be found after update (unlikely but possible)
st.session_state.selected_resource = None
st.session_state.view_type = 'list' # Go back to list if VM disappears
st.session_state.selected_resource_type = 'virtual_machine' # Corrected resource type
st.session_state.selected_resource = updated_vm
else:
st.session_state.selected_resource = None
st.session_state.view_type = 'list'
st.session_state.selected_resource_type = 'virtual_machine'
st.rerun()
# Delete VM Workload Button (Now redundant with popover action, consider removing)
# Attached Volumes
st.subheader("Attached Volumes")
volumes = VirtualMachine.get("volumes", [])
if volumes:
for vol in volumes:
st.markdown(
f"- **ID:** `{vol['id']}` | **Name:** {vol.get('name', 'N/A')} | "
f"**Size:** {vol.get('size_gb', 'N/A')} GB | **Status:** {vol.get('status', 'N/A')}"
)
else:
st.info("No volumes attached to this VM.")
# Attached Network Ports
st.subheader("Network Ports")
ports = VirtualMachine.get("network_ports", [])
if ports:
for port in ports:
network = port.get("network", {})
st.markdown(f"""
- **Port ID:** `{port['id']}`
**IP:** {port.get('ip_address', 'N/A')}
**MAC:** {port.get('mac_address', 'N/A')}
**Type:** {port.get('port_type', 'N/A')}
**Network:** `{network.get('id', 'N/A')}` ({network.get('name', 'Unnamed')})
**CIDR:** {network.get('cidr', 'N/A') if network else 'N/A'}
""")
else:
st.info("No network ports attached to this VM.")
# Delete button
if st.button("Delete VM Workload"):
result = st.session_state.client.delete_virtual_machine(VirtualMachine['id'])
if result:
st.success("VM workload deleted successfully!")
st.session_state.selected_resource = None
st.session_state.view_type = 'list'
st.session_state.selected_resource_type = 'virtual_machine' # Corrected resource type
st.session_state.selected_resource_type = 'virtual_machine'
st.rerun()
+2 -1
View File
@@ -20,4 +20,5 @@ netifaces
jwt
docker
python-dotenv
psutil
psutil
packaging