agave_validator/commands/exit/
mod.rs1use {
2 crate::{
3 admin_rpc_service,
4 commands::{monitor, wait_for_restart_window, FromClapArgMatches, Result},
5 },
6 clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand},
7 solana_clap_utils::input_validators::{is_parsable, is_valid_percentage},
8 std::path::Path,
9};
10
11const COMMAND: &str = "exit";
12
13const DEFAULT_MIN_IDLE_TIME: &str = "10";
14const DEFAULT_MAX_DELINQUENT_STAKE: &str = "5";
15
16#[derive(Debug, PartialEq)]
17pub struct ExitArgs {
18 pub force: bool,
19 pub monitor: bool,
20 pub min_idle_time: usize,
21 pub max_delinquent_stake: u8,
22 pub skip_new_snapshot_check: bool,
23 pub skip_health_check: bool,
24}
25
26impl FromClapArgMatches for ExitArgs {
27 fn from_clap_arg_match(matches: &ArgMatches) -> Result<Self> {
28 Ok(ExitArgs {
29 force: matches.is_present("force"),
30 monitor: matches.is_present("monitor"),
31 min_idle_time: value_t_or_exit!(matches, "min_idle_time", usize),
32 max_delinquent_stake: value_t_or_exit!(matches, "max_delinquent_stake", u8),
33 skip_new_snapshot_check: matches.is_present("skip_new_snapshot_check"),
34 skip_health_check: matches.is_present("skip_health_check"),
35 })
36 }
37}
38
39pub fn command<'a>() -> App<'a, 'a> {
40 SubCommand::with_name(COMMAND)
41 .about("Send an exit request to the validator")
42 .arg(
43 Arg::with_name("force")
44 .short("f")
45 .long("force")
46 .takes_value(false)
47 .help(
48 "Request the validator exit immediately instead of waiting for a restart window",
49 ),
50 )
51 .arg(
52 Arg::with_name("monitor")
53 .short("m")
54 .long("monitor")
55 .takes_value(false)
56 .help("Monitor the validator after sending the exit request"),
57 )
58 .arg(
59 Arg::with_name("min_idle_time")
60 .long("min-idle-time")
61 .takes_value(true)
62 .validator(is_parsable::<usize>)
63 .value_name("MINUTES")
64 .default_value(DEFAULT_MIN_IDLE_TIME)
65 .help(
66 "Minimum time that the validator should not be leader before restarting",
67 ),
68 )
69 .arg(
70 Arg::with_name("max_delinquent_stake")
71 .long("max-delinquent-stake")
72 .takes_value(true)
73 .validator(is_valid_percentage)
74 .default_value(DEFAULT_MAX_DELINQUENT_STAKE)
75 .value_name("PERCENT")
76 .help("The maximum delinquent stake % permitted for an exit"),
77 )
78 .arg(
79 Arg::with_name("skip_new_snapshot_check")
80 .long("skip-new-snapshot-check")
81 .help("Skip check for a new snapshot"),
82 )
83 .arg(
84 Arg::with_name("skip_health_check")
85 .long("skip-health-check")
86 .help("Skip health check"),
87 )
88}
89
90pub fn execute(matches: &ArgMatches, ledger_path: &Path) -> Result<()> {
91 let exit_args = ExitArgs::from_clap_arg_match(matches)?;
92
93 if !exit_args.force {
94 wait_for_restart_window::wait_for_restart_window(
95 ledger_path,
96 None,
97 exit_args.min_idle_time,
98 exit_args.max_delinquent_stake,
99 exit_args.skip_new_snapshot_check,
100 exit_args.skip_health_check,
101 )?;
102 }
103
104 let admin_client = admin_rpc_service::connect(ledger_path);
105 admin_rpc_service::runtime().block_on(async move { admin_client.await?.exit().await })?;
106 println!("Exit request sent");
107
108 if exit_args.monitor {
109 monitor::execute(matches, ledger_path)?;
110 }
111
112 Ok(())
113}
114
115#[cfg(test)]
116mod tests {
117 use {super::*, crate::commands::tests::verify_args_struct_by_command};
118
119 impl Default for ExitArgs {
120 fn default() -> Self {
121 ExitArgs {
122 min_idle_time: DEFAULT_MIN_IDLE_TIME
123 .parse()
124 .expect("invalid DEFAULT_MIN_IDLE_TIME"),
125 max_delinquent_stake: DEFAULT_MAX_DELINQUENT_STAKE
126 .parse()
127 .expect("invalid DEFAULT_MAX_DELINQUENT_STAKE"),
128 force: false,
129 monitor: false,
130 skip_new_snapshot_check: false,
131 skip_health_check: false,
132 }
133 }
134 }
135
136 #[test]
137 fn verify_args_struct_by_command_exit_default() {
138 verify_args_struct_by_command(command(), vec![COMMAND], ExitArgs::default());
139 }
140
141 #[test]
142 fn verify_args_struct_by_command_exit_with_force() {
143 verify_args_struct_by_command(
144 command(),
145 vec![COMMAND, "--force"],
146 ExitArgs {
147 force: true,
148 ..ExitArgs::default()
149 },
150 );
151 }
152
153 #[test]
154 fn verify_args_struct_by_command_exit_with_monitor() {
155 verify_args_struct_by_command(
156 command(),
157 vec![COMMAND, "--monitor"],
158 ExitArgs {
159 monitor: true,
160 ..ExitArgs::default()
161 },
162 );
163 }
164
165 #[test]
166 fn verify_args_struct_by_command_exit_with_min_idle_time() {
167 verify_args_struct_by_command(
168 command(),
169 vec![COMMAND, "--min-idle-time", "60"],
170 ExitArgs {
171 min_idle_time: 60,
172 ..ExitArgs::default()
173 },
174 );
175 }
176
177 #[test]
178 fn verify_args_struct_by_command_exit_with_max_delinquent_stake() {
179 verify_args_struct_by_command(
180 command(),
181 vec![COMMAND, "--max-delinquent-stake", "10"],
182 ExitArgs {
183 max_delinquent_stake: 10,
184 ..ExitArgs::default()
185 },
186 );
187 }
188
189 #[test]
190 fn verify_args_struct_by_command_exit_with_skip_new_snapshot_check() {
191 verify_args_struct_by_command(
192 command(),
193 vec![COMMAND, "--skip-new-snapshot-check"],
194 ExitArgs {
195 skip_new_snapshot_check: true,
196 ..ExitArgs::default()
197 },
198 );
199 }
200
201 #[test]
202 fn verify_args_struct_by_command_exit_with_skip_health_check() {
203 verify_args_struct_by_command(
204 command(),
205 vec![COMMAND, "--skip-health-check"],
206 ExitArgs {
207 skip_health_check: true,
208 ..ExitArgs::default()
209 },
210 );
211 }
212}