meta_srv/service/admin/
maintenance.rs1use std::collections::HashMap;
16
17use axum::extract::State;
18use axum::response::{IntoResponse, Response};
19use axum::Json;
20use common_meta::key::runtime_switch::RuntimeSwitchManagerRef;
21use common_telemetry::{info, warn};
22use serde::{Deserialize, Serialize};
23use snafu::{OptionExt, ResultExt};
24use tonic::codegen::http;
25
26use crate::error::{
27 self, MissingRequiredParameterSnafu, ParseBoolSnafu, Result, RuntimeSwitchManagerSnafu,
28 UnsupportedSnafu,
29};
30use crate::service::admin::util::{to_json_response, to_not_found_response, ErrorHandler};
31use crate::service::admin::HttpHandler;
32
33#[derive(Clone)]
34pub struct MaintenanceHandler {
35 pub manager: RuntimeSwitchManagerRef,
36}
37
38#[derive(Debug, Serialize, Deserialize)]
39pub(crate) struct MaintenanceResponse {
40 enabled: bool,
41}
42
43#[axum_macros::debug_handler]
45pub(crate) async fn status(State(handler): State<MaintenanceHandler>) -> Response {
46 handler
47 .get_maintenance()
48 .await
49 .map(Json)
50 .map_err(ErrorHandler::new)
51 .into_response()
52}
53
54#[axum_macros::debug_handler]
56pub(crate) async fn set(State(handler): State<MaintenanceHandler>) -> Response {
57 handler
58 .set_maintenance()
59 .await
60 .map(Json)
61 .map_err(ErrorHandler::new)
62 .into_response()
63}
64
65#[axum_macros::debug_handler]
67pub(crate) async fn unset(State(handler): State<MaintenanceHandler>) -> Response {
68 handler
69 .unset_maintenance()
70 .await
71 .map(Json)
72 .map_err(ErrorHandler::new)
73 .into_response()
74}
75
76impl TryFrom<MaintenanceResponse> for String {
77 type Error = error::Error;
78
79 fn try_from(response: MaintenanceResponse) -> Result<Self> {
80 serde_json::to_string(&response).context(error::SerializeToJsonSnafu {
81 input: format!("{response:?}"),
82 })
83 }
84}
85
86impl MaintenanceHandler {
87 pub(crate) async fn get_maintenance(&self) -> crate::Result<MaintenanceResponse> {
88 let enabled = self
89 .manager
90 .maintenance_mode()
91 .await
92 .context(RuntimeSwitchManagerSnafu)?;
93 Ok(MaintenanceResponse { enabled })
94 }
95
96 pub(crate) async fn set_maintenance(&self) -> crate::Result<MaintenanceResponse> {
97 self.manager
98 .set_maintenance_mode()
99 .await
100 .context(RuntimeSwitchManagerSnafu)?;
101 info!("Enable the maintenance mode.");
103 Ok(MaintenanceResponse { enabled: true })
104 }
105
106 pub(crate) async fn unset_maintenance(&self) -> crate::Result<MaintenanceResponse> {
107 self.manager
108 .unset_maintenance_mode()
109 .await
110 .context(RuntimeSwitchManagerSnafu)?;
111 info!("Disable the maintenance mode.");
113 Ok(MaintenanceResponse { enabled: false })
114 }
115
116 async fn handle_legacy_maintenance(
117 &self,
118 params: &HashMap<String, String>,
119 ) -> crate::Result<MaintenanceResponse> {
120 let enable = get_enable_from_params(params)?;
121 if enable {
122 self.set_maintenance().await
123 } else {
124 self.unset_maintenance().await
125 }
126 }
127}
128
129fn get_enable_from_params(params: &HashMap<String, String>) -> crate::Result<bool> {
130 params
131 .get("enable")
132 .map(|v| v.parse::<bool>())
133 .context(MissingRequiredParameterSnafu { param: "enable" })?
134 .context(ParseBoolSnafu {
135 err_msg: "'enable' must be 'true' or 'false'",
136 })
137}
138
139const MAINTENANCE_PATH: &str = "maintenance";
140const ENABLE_SUFFIX: &str = "enable";
141const DISABLE_SUFFIX: &str = "disable";
142const STATUS_SUFFIX: &str = "status";
143
144#[async_trait::async_trait]
145impl HttpHandler for MaintenanceHandler {
146 async fn handle(
149 &self,
150 path: &str,
151 method: http::Method,
152 params: &HashMap<String, String>,
153 ) -> crate::Result<http::Response<String>> {
154 match method {
155 http::Method::GET => {
156 if path.ends_with(STATUS_SUFFIX) {
157 let response = self.get_maintenance().await?;
159 to_json_response(response)
160 } else if path.ends_with(MAINTENANCE_PATH) && params.is_empty() {
161 let response = self.get_maintenance().await?;
163 to_json_response(response)
164 } else if path.ends_with(MAINTENANCE_PATH) {
165 warn!(
167 "Found URL parameters in '/admin/maintenance' request, it's deprecated, will be removed in the future"
168 );
169 let response = self.handle_legacy_maintenance(params).await?;
172 to_json_response(response)
173 } else {
174 to_not_found_response()
175 }
176 }
177 http::Method::PUT => {
178 if path.ends_with(MAINTENANCE_PATH) {
180 warn!("Found PUT request to '/admin/maintenance', it's deprecated, will be removed in the future");
181 let response = self.handle_legacy_maintenance(params).await?;
182 to_json_response(response)
183 } else {
184 to_not_found_response()
185 }
186 }
187 http::Method::POST => {
188 if path.ends_with(ENABLE_SUFFIX) {
190 let response = self.set_maintenance().await?;
191 to_json_response(response)
192 } else if path.ends_with(DISABLE_SUFFIX) {
193 let response = self.unset_maintenance().await?;
195 to_json_response(response)
196 } else if path.ends_with(MAINTENANCE_PATH) {
197 warn!("Found PUT request to '/admin/maintenance', it's deprecated, will be removed in the future");
199 let response = self.handle_legacy_maintenance(params).await?;
200 to_json_response(response)
201 } else {
202 to_not_found_response()
203 }
204 }
205 _ => UnsupportedSnafu {
206 operation: format!("http method {method}"),
207 }
208 .fail(),
209 }
210 }
211}