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(
78 infra: &InfraContext<'_>,
79 token: &str,
80 params: &ApplyPowerParams,
81) -> Result<TransitionStartOutput, Error> {
82 validate_user_group_members_access(infra, token, ¶ms.xnames).await?;
83
84 infra
85 .backend
86 .pcs_transitions_post(
87 token,
88 pcs_operation(params.action, params.force),
89 ¶ms.xnames,
90 )
91 .await
92}
93
94pub(crate) fn pcs_operation(action: PowerAction, force: bool) -> &'static str {
100 match (action, force) {
101 (PowerAction::On, _) => "on",
102 (PowerAction::Off, false) => "soft-off",
103 (PowerAction::Off, true) => "force-off",
104 (PowerAction::Reset, false) => "soft-restart",
105 (PowerAction::Reset, true) => "hard-restart",
106 }
107}
108
109pub async fn get_power_transition(
119 infra: &InfraContext<'_>,
120 token: &str,
121 transition_id: &str,
122) -> Result<TransitionResponse, Error> {
123 let transition = infra
124 .backend
125 .pcs_transitions_get(token, transition_id)
126 .await?;
127
128 let xnames: Vec<String> =
129 transition.tasks.iter().map(|t| t.xname.clone()).collect();
130 validate_user_group_members_access(infra, token, &xnames).await?;
131
132 Ok(transition)
133}
134
135#[cfg(test)]
136mod tests {
137 use super::{PowerAction, pcs_operation};
142
143 #[test]
144 fn on_ignores_force_flag() {
145 assert_eq!(pcs_operation(PowerAction::On, false), "on");
148 assert_eq!(pcs_operation(PowerAction::On, true), "on");
149 }
150
151 #[test]
152 fn off_distinguishes_soft_from_force() {
153 assert_eq!(pcs_operation(PowerAction::Off, false), "soft-off");
154 assert_eq!(pcs_operation(PowerAction::Off, true), "force-off");
155 }
156
157 #[test]
158 fn reset_distinguishes_soft_from_hard() {
159 assert_eq!(pcs_operation(PowerAction::Reset, false), "soft-restart");
160 assert_eq!(pcs_operation(PowerAction::Reset, true), "hard-restart");
161 }
162}