manta_server/service/
power.rs1use manta_backend_dispatcher::error::Error;
10use manta_backend_dispatcher::interfaces::hsm::group::GroupTrait;
11use manta_backend_dispatcher::interfaces::pcs::PCSTrait;
12use manta_backend_dispatcher::types::pcs::transitions::types::{
13 TransitionResponse, TransitionStartOutput,
14};
15
16use crate::server::common::app_context::InfraContext;
17use crate::service::authorization::validate_user_group_members_access;
18use crate::service::node_ops;
19pub use manta_shared::types::api::power::{
20 ApplyPowerParams, PowerAction, PowerTargetType,
21};
22
23pub async fn resolve_target_xnames(
34 infra: &InfraContext<'_>,
35 token: &str,
36 target_type: PowerTargetType,
37 host_expression: &str,
38) -> Result<Vec<String>, Error> {
39 let xnames = match target_type {
40 PowerTargetType::Cluster => {
41 infra
42 .backend
43 .get_member_vec_from_group_name_vec(
44 token,
45 std::slice::from_ref(&host_expression.to_string()),
46 )
47 .await?
48 }
49 PowerTargetType::Nodes => {
50 node_ops::from_user_hosts_expression_to_xname_vec(
51 infra,
52 token,
53 host_expression,
54 false,
55 )
56 .await?
57 }
58 };
59
60 validate_user_group_members_access(infra, token, &xnames).await?;
61
62 if xnames.is_empty() {
63 return Err(Error::BadRequest("No nodes to operate on".into()));
64 }
65
66 Ok(xnames)
67}
68
69pub async fn apply_power(
86 infra: &InfraContext<'_>,
87 token: &str,
88 params: &ApplyPowerParams,
89) -> Result<TransitionStartOutput, Error> {
90 validate_user_group_members_access(infra, token, ¶ms.xnames).await?;
91
92 infra
93 .backend
94 .pcs_transitions_post(
95 token,
96 pcs_operation(params.action, params.force),
97 ¶ms.xnames,
98 )
99 .await
100}
101
102pub(crate) fn pcs_operation(action: PowerAction, force: bool) -> &'static str {
108 match (action, force) {
109 (PowerAction::On, _) => "on",
110 (PowerAction::Off, false) => "soft-off",
111 (PowerAction::Off, true) => "force-off",
112 (PowerAction::Reset, false) => "soft-restart",
113 (PowerAction::Reset, true) => "hard-restart",
114 }
115}
116
117pub async fn get_power_transition(
127 infra: &InfraContext<'_>,
128 token: &str,
129 transition_id: &str,
130) -> Result<TransitionResponse, Error> {
131 let transition = infra
132 .backend
133 .pcs_transitions_get(token, transition_id)
134 .await?;
135
136 let xnames: Vec<String> =
137 transition.tasks.iter().map(|t| t.xname.clone()).collect();
138 validate_user_group_members_access(infra, token, &xnames).await?;
139
140 Ok(transition)
141}
142
143#[cfg(test)]
144mod tests {
145 use super::{PowerAction, pcs_operation};
150
151 #[test]
152 fn on_ignores_force_flag() {
153 assert_eq!(pcs_operation(PowerAction::On, false), "on");
156 assert_eq!(pcs_operation(PowerAction::On, true), "on");
157 }
158
159 #[test]
160 fn off_distinguishes_soft_from_force() {
161 assert_eq!(pcs_operation(PowerAction::Off, false), "soft-off");
162 assert_eq!(pcs_operation(PowerAction::Off, true), "force-off");
163 }
164
165 #[test]
166 fn reset_distinguishes_soft_from_hard() {
167 assert_eq!(pcs_operation(PowerAction::Reset, false), "soft-restart");
168 assert_eq!(pcs_operation(PowerAction::Reset, true), "hard-restart");
169 }
170}