When a VM starts it;s ports are set to active
Added network and volume details to streamlit
This commit is contained in:
@@ -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
+1
Submodule sandbox/joke_container added at 4a8d0ac064
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -20,4 +20,5 @@ netifaces
|
||||
jwt
|
||||
docker
|
||||
python-dotenv
|
||||
psutil
|
||||
psutil
|
||||
packaging
|
||||
Reference in New Issue
Block a user