meta_srv/service/admin/
maintenance.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16
17use common_meta::key::runtime_switch::RuntimeSwitchManagerRef;
18use common_telemetry::{info, warn};
19use serde::{Deserialize, Serialize};
20use snafu::{OptionExt, ResultExt};
21use tonic::codegen::http;
22use tonic::codegen::http::Response;
23
24use crate::error::{
25    self, MissingRequiredParameterSnafu, ParseBoolSnafu, Result, RuntimeSwitchManagerSnafu,
26    UnsupportedSnafu,
27};
28use crate::service::admin::util::{to_json_response, to_not_found_response};
29use crate::service::admin::HttpHandler;
30
31#[derive(Clone)]
32pub struct MaintenanceHandler {
33    pub manager: RuntimeSwitchManagerRef,
34}
35
36#[derive(Debug, Serialize, Deserialize)]
37struct MaintenanceResponse {
38    enabled: bool,
39}
40
41impl TryFrom<MaintenanceResponse> for String {
42    type Error = error::Error;
43
44    fn try_from(response: MaintenanceResponse) -> Result<Self> {
45        serde_json::to_string(&response).context(error::SerializeToJsonSnafu {
46            input: format!("{response:?}"),
47        })
48    }
49}
50
51impl MaintenanceHandler {
52    async fn get_maintenance(&self) -> crate::Result<MaintenanceResponse> {
53        let enabled = self
54            .manager
55            .maintenance_mode()
56            .await
57            .context(RuntimeSwitchManagerSnafu)?;
58        Ok(MaintenanceResponse { enabled })
59    }
60
61    async fn set_maintenance(&self) -> crate::Result<MaintenanceResponse> {
62        self.manager
63            .set_maintenance_mode()
64            .await
65            .context(RuntimeSwitchManagerSnafu)?;
66        // TODO(weny): Add a record to the system events.
67        info!("Enable the maintenance mode.");
68        Ok(MaintenanceResponse { enabled: true })
69    }
70
71    async fn unset_maintenance(&self) -> crate::Result<MaintenanceResponse> {
72        self.manager
73            .unset_maintenance_mode()
74            .await
75            .context(RuntimeSwitchManagerSnafu)?;
76        // TODO(weny): Add a record to the system events.
77        info!("Disable the maintenance mode.");
78        Ok(MaintenanceResponse { enabled: false })
79    }
80
81    async fn handle_legacy_maintenance(
82        &self,
83        params: &HashMap<String, String>,
84    ) -> crate::Result<MaintenanceResponse> {
85        let enable = get_enable_from_params(params)?;
86        if enable {
87            self.set_maintenance().await
88        } else {
89            self.unset_maintenance().await
90        }
91    }
92}
93
94fn get_enable_from_params(params: &HashMap<String, String>) -> crate::Result<bool> {
95    params
96        .get("enable")
97        .map(|v| v.parse::<bool>())
98        .context(MissingRequiredParameterSnafu { param: "enable" })?
99        .context(ParseBoolSnafu {
100            err_msg: "'enable' must be 'true' or 'false'",
101        })
102}
103
104const MAINTENANCE_PATH: &str = "maintenance";
105const ENABLE_SUFFIX: &str = "enable";
106const DISABLE_SUFFIX: &str = "disable";
107const STATUS_SUFFIX: &str = "status";
108
109#[async_trait::async_trait]
110impl HttpHandler for MaintenanceHandler {
111    // TODO(weny): Remove the legacy version of the maintenance API.
112    // However, we need to keep the legacy version for a while to avoid breaking the existing operators.
113    async fn handle(
114        &self,
115        path: &str,
116        method: http::Method,
117        params: &HashMap<String, String>,
118    ) -> crate::Result<Response<String>> {
119        match method {
120            http::Method::GET => {
121                if path.ends_with(STATUS_SUFFIX) {
122                    // Handle GET request to '/admin/maintenance/status'
123                    let response = self.get_maintenance().await?;
124                    to_json_response(response)
125                } else if path.ends_with(MAINTENANCE_PATH) && params.is_empty() {
126                    // Handle GET request to '/admin/maintenance'. (The legacy version)
127                    let response = self.get_maintenance().await?;
128                    to_json_response(response)
129                } else if path.ends_with(MAINTENANCE_PATH) {
130                    // Handle GET request to '/admin/maintenance' with URL parameters. (The legacy version)
131                    warn!(
132                        "Found URL parameters in '/admin/maintenance' request, it's deprecated, will be removed in the future"
133                    );
134                    // The old version operator will send GET request with URL parameters,
135                    // so we need to support it.
136                    let response = self.handle_legacy_maintenance(params).await?;
137                    to_json_response(response)
138                } else {
139                    to_not_found_response()
140                }
141            }
142            http::Method::PUT => {
143                // Handle PUT request to '/admin/maintenance' with URL parameters. (The legacy version)
144                if path.ends_with(MAINTENANCE_PATH) {
145                    warn!("Found PUT request to '/admin/maintenance', it's deprecated, will be removed in the future");
146                    let response = self.handle_legacy_maintenance(params).await?;
147                    to_json_response(response)
148                } else {
149                    to_not_found_response()
150                }
151            }
152            http::Method::POST => {
153                // Handle POST request to '/admin/maintenance/enable'
154                if path.ends_with(ENABLE_SUFFIX) {
155                    let response = self.set_maintenance().await?;
156                    to_json_response(response)
157                } else if path.ends_with(DISABLE_SUFFIX) {
158                    // Handle POST request to '/admin/maintenance/disable'
159                    let response = self.unset_maintenance().await?;
160                    to_json_response(response)
161                } else if path.ends_with(MAINTENANCE_PATH) {
162                    // Handle POST request to '/admin/maintenance' with URL parameters. (The legacy version)
163                    warn!("Found PUT request to '/admin/maintenance', it's deprecated, will be removed in the future");
164                    let response = self.handle_legacy_maintenance(params).await?;
165                    to_json_response(response)
166                } else {
167                    to_not_found_response()
168                }
169            }
170            _ => UnsupportedSnafu {
171                operation: format!("http method {method}"),
172            }
173            .fail(),
174        }
175    }
176}