use axum::{ extract::{Path, State}, http::StatusCode, response::{IntoResponse, Response}, routing::{get, post}, Json, Router, }; use tracing::{info, warn}; use std::sync::Arc; use super::{empty_collection, validate_id, service_unavailable, resource_not_found, RESOURCE_ID}; use super::super::schema::*; use crate::state::AppState; pub(crate) fn router(state: Arc) -> Router> { Router::new() .route( "/v1/Managers/{manager_id}/VirtualMedia", get(virtual_media_collection), ) .route( "/v1/Managers/{manager_id}/VirtualMedia/{media_id}", get(virtual_media_detail), ) .route( "/v1/Managers/{manager_id}/VirtualMedia/{media_id}/Actions/VirtualMedia.InsertMedia", post(virtual_media_insert), ) .route( "/v1/Managers/{manager_id}/VirtualMedia/{media_id}/Actions/VirtualMedia.EjectMedia", post(virtual_media_eject), ) .with_state(state) } async fn virtual_media_collection(Path(manager_id): Path) -> Response { if let Some(resp) = validate_id(&manager_id) { return resp; } Json(empty_collection( "#VirtualMediaCollection.VirtualMediaCollection", &format!("/redfish/v1/Managers/{}/VirtualMedia", manager_id), "/redfish/v1/$metadata#VirtualMediaCollection.VirtualMediaCollection", "Virtual Media Collection", "Collection of Virtual Media", vec![odata_ref(&format!( "/redfish/v1/Managers/{}/VirtualMedia/{}", manager_id, RESOURCE_ID ))], )) .into_response() } async fn virtual_media_detail( State(state): State>, Path((manager_id, media_id)): Path<(String, String)>, ) -> Response { if let Some(resp) = validate_id(&manager_id) { return resp; } if media_id != RESOURCE_ID { return resource_not_found(); } let (inserted, image_name, connected_via) = { let guard = state.msd.read().await; match guard.as_ref() { Some(msd) => { let msd_state = msd.state().await; let img_name = msd_state .current_image .as_ref() .map(|i| i.name.clone()) .or_else(|| { msd_state .drive_info .as_ref() .map(|_| "Virtual Drive".to_string()) }); ( msd_state.connected, img_name, if msd_state.connected { Some("Applet".to_string()) } else { None }, ) } None => (false, None, None), } }; Json(VirtualMedia { odata_type: "#VirtualMedia.v1_6_2.VirtualMedia".to_string(), odata_id: format!("/redfish/v1/Managers/{}/VirtualMedia/{}", manager_id, media_id), odata_context: "/redfish/v1/$metadata#VirtualMedia.VirtualMedia".to_string(), id: media_id.clone(), name: "Virtual Media 1".to_string(), description: "Virtual Media Device".to_string(), media_types: vec!["CD".to_string(), "USBStick".to_string()], connected_via: connected_via, inserted: inserted, image: None, image_name: image_name, write_protected: true, transfer_method: None, transfer_protocol_type: None, status: if inserted { Status::enabled_ok() } else { Status::disabled_ok() }, actions: VirtualMediaActions { insert_media: ActionTarget { target: format!( "/redfish/v1/Managers/{}/VirtualMedia/{}/Actions/VirtualMedia.InsertMedia", manager_id, media_id ), }, eject_media: ActionTarget { target: format!( "/redfish/v1/Managers/{}/VirtualMedia/{}/Actions/VirtualMedia.EjectMedia", manager_id, media_id ), }, }, }) .into_response() } async fn virtual_media_insert( State(state): State>, Path((manager_id, media_id)): Path<(String, String)>, Json(req): Json, ) -> Response { if let Some(resp) = validate_id(&manager_id) { return resp; } if media_id != RESOURCE_ID { return resource_not_found(); } let result = { let guard = state.msd.read().await; let msd = match guard.as_ref() { Some(msd) => msd, None => return service_unavailable("MSD not available"), }; if msd.state().await.connected { return ( StatusCode::CONFLICT, Json(RedfishError::general_error("Virtual media already inserted")), ) .into_response(); } info!("Redfish: VirtualMedia.InsertMedia image='{}'", req.image); msd.connect_drive().await }; match result { Ok(()) => { info!("Redfish: VirtualMedia.InsertMedia executed"); StatusCode::NO_CONTENT.into_response() } Err(e) => { warn!("Redfish: VirtualMedia.InsertMedia failed: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(RedfishError::general_error(&e.to_string())), ) .into_response() } } } async fn virtual_media_eject( State(state): State>, Path((manager_id, media_id)): Path<(String, String)>, ) -> Response { if let Some(resp) = validate_id(&manager_id) { return resp; } if media_id != RESOURCE_ID { return resource_not_found(); } let result = { let guard = state.msd.read().await; let msd = match guard.as_ref() { Some(msd) => msd, None => return service_unavailable("MSD not available"), }; if !msd.state().await.connected { return ( StatusCode::CONFLICT, Json(RedfishError::general_error("No virtual media inserted")), ) .into_response(); } msd.disconnect().await }; match result { Ok(()) => { info!("Redfish: VirtualMedia.EjectMedia executed"); StatusCode::NO_CONTENT.into_response() } Err(e) => { warn!("Redfish: VirtualMedia.EjectMedia failed: {}", e); ( StatusCode::INTERNAL_SERVER_ERROR, Json(RedfishError::general_error(&e.to_string())), ) .into_response() } } }