1use super::{
8 extra_vars::ExtraVarsDocBuilder,
9 inventory::{
10 generate_full_cone_private_node_static_environment_inventory,
11 generate_port_restricted_cone_private_node_static_environment_inventory,
12 generate_symmetric_private_node_static_environment_inventory,
13 },
14 AnsibleInventoryType, AnsiblePlaybook, AnsibleRunner,
15};
16use crate::{
17 ansible::inventory::{
18 generate_custom_environment_inventory,
19 generate_full_cone_nat_gateway_static_environment_inventory,
20 generate_port_restricted_cone_nat_gateway_static_environment_inventory,
21 },
22 bootstrap::BootstrapOptions,
23 clients::ClientsDeployOptions,
24 deploy::DeployOptions,
25 error::{Error, Result},
26 funding::FundingOptions,
27 inventory::{DeploymentNodeRegistries, VirtualMachine},
28 print_duration, run_external_command, BinaryOption, CloudProvider, EvmNetwork, LogFormat,
29 NodeType, SshClient, UpgradeOptions,
30};
31use ant_service_management::NodeRegistryManager;
32use evmlib::common::U256;
33use log::{debug, error, trace};
34use semver::Version;
35use serde::{Deserialize, Serialize};
36use std::{
37 collections::HashMap,
38 net::IpAddr,
39 path::PathBuf,
40 time::{Duration, Instant},
41};
42use walkdir::WalkDir;
43
44use crate::ansible::extra_vars;
45
46pub const DEFAULT_BETA_ENCRYPTION_KEY: &str =
47 "49113d2083f57a976076adbe85decb75115820de1e6e74b47e0429338cef124a";
48
49#[derive(Clone, Serialize, Deserialize)]
50pub struct ProvisionOptions {
51 pub ant_version: Option<String>,
55 pub binary_option: BinaryOption,
56 pub chunk_size: Option<u64>,
57 pub chunk_tracker_data_addresses: Option<Vec<String>>,
58 pub chunk_tracker_services: Option<u16>,
59 pub client_env_variables: Option<Vec<(String, String)>>,
60 pub delayed_verifier_batch_size: Option<u16>,
61 pub disable_nodes: bool,
62 pub delayed_verifier_quorum_value: Option<String>,
63 pub start_delayed_verifier: bool,
64 pub enable_logging: bool,
65 pub enable_metrics: bool,
66 pub start_random_verifier: bool,
67 pub start_performance_verifier: bool,
68 pub start_uploaders: bool,
69 pub evm_data_payments_address: Option<String>,
70 pub evm_merkle_payments_address: Option<String>,
71 pub evm_network: EvmNetwork,
72 pub evm_payment_token_address: Option<String>,
73 pub evm_rpc_url: Option<String>,
74 pub expected_hash: Option<String>,
75 pub expected_size: Option<u64>,
76 pub file_address: Option<String>,
77 pub full_cone_private_node_count: u16,
78 pub funding_wallet_secret_key: Option<String>,
79 pub gas_amount: Option<U256>,
80 pub interval: Option<Duration>,
81 pub log_format: Option<LogFormat>,
82 pub max_archived_log_files: u16,
83 pub max_log_files: u16,
84 pub max_uploads: Option<u32>,
85 pub merkle: bool,
86 pub name: String,
87 pub network_id: Option<u8>,
88 pub network_dashboard_branch: Option<String>,
89 pub node_count: u16,
90 pub node_env_variables: Option<Vec<(String, String)>>,
91 pub output_inventory_dir_path: PathBuf,
92 pub peer_cache_node_count: u16,
93 pub performance_verifier_batch_size: Option<u16>,
94 pub port_restricted_cone_private_node_count: u16,
95 pub public_rpc: bool,
96 pub random_verifier_batch_size: Option<u16>,
97 pub repair_service_count: u16,
98 pub data_retrieval_service_count: u16,
99 pub rewards_address: Option<String>,
100 pub scan_frequency: Option<u64>,
101 pub sleep_duration: Option<u16>,
102 pub single_node_payment: bool,
103 pub start_chunk_trackers: bool,
104 pub start_data_retrieval: bool,
105 pub symmetric_private_node_count: u16,
106 pub token_amount: Option<U256>,
107 pub upload_batch_size: Option<u16>,
108 pub upload_size: Option<u16>,
109 pub upload_interval: Option<u16>,
110 pub uploaders_count: Option<u16>,
111 pub upnp_private_node_count: u16,
112 pub wallet_secret_keys: Option<Vec<String>>,
113}
114
115#[derive(Clone, Debug)]
117pub struct PrivateNodeProvisionInventory {
118 pub full_cone_nat_gateway_vms: Vec<VirtualMachine>,
119 pub full_cone_private_node_vms: Vec<VirtualMachine>,
120 pub symmetric_nat_gateway_vms: Vec<VirtualMachine>,
121 pub symmetric_private_node_vms: Vec<VirtualMachine>,
122 pub port_restricted_cone_nat_gateway_vms: Vec<VirtualMachine>,
123 pub port_restricted_cone_private_node_vms: Vec<VirtualMachine>,
124}
125
126impl PrivateNodeProvisionInventory {
127 pub fn new(
128 provisioner: &AnsibleProvisioner,
129 full_cone_private_node_vm_count: Option<u16>,
130 symmetric_private_node_vm_count: Option<u16>,
131 port_restricted_cone_private_node_vm_count: Option<u16>,
132 ) -> Result<Self> {
133 let should_provision_full_cone_private_nodes = full_cone_private_node_vm_count
135 .map(|count| count > 0)
136 .unwrap_or(true);
137 let should_provision_symmetric_private_nodes = symmetric_private_node_vm_count
138 .map(|count| count > 0)
139 .unwrap_or(true);
140 let should_provision_port_restricted_cone_private_nodes =
141 port_restricted_cone_private_node_vm_count
142 .map(|count| count > 0)
143 .unwrap_or(true);
144
145 let mut inventory = Self {
146 full_cone_nat_gateway_vms: Default::default(),
147 full_cone_private_node_vms: Default::default(),
148 symmetric_nat_gateway_vms: Default::default(),
149 symmetric_private_node_vms: Default::default(),
150 port_restricted_cone_nat_gateway_vms: Default::default(),
151 port_restricted_cone_private_node_vms: Default::default(),
152 };
153
154 if should_provision_full_cone_private_nodes {
155 let full_cone_private_node_vms = provisioner
156 .ansible_runner
157 .get_inventory(AnsibleInventoryType::FullConePrivateNodes, true)
158 .inspect_err(|err| {
159 println!("Failed to obtain the inventory of Full Cone private node: {err:?}");
160 })?;
161
162 let full_cone_nat_gateway_inventory = provisioner
163 .ansible_runner
164 .get_inventory(AnsibleInventoryType::FullConeNatGateway, true)
165 .inspect_err(|err| {
166 println!("Failed to get Full Cone NAT Gateway inventory {err:?}");
167 })?;
168
169 if full_cone_nat_gateway_inventory.len() != full_cone_private_node_vms.len() {
170 println!("The number of Full Cone private nodes does not match the number of Full Cone NAT Gateway VMs");
171 return Err(Error::VmCountMismatch(
172 Some(AnsibleInventoryType::FullConePrivateNodes),
173 Some(AnsibleInventoryType::FullConeNatGateway),
174 ));
175 }
176
177 inventory.full_cone_private_node_vms = full_cone_private_node_vms;
178 inventory.full_cone_nat_gateway_vms = full_cone_nat_gateway_inventory;
179 }
180
181 if should_provision_symmetric_private_nodes {
182 let symmetric_private_node_vms = provisioner
183 .ansible_runner
184 .get_inventory(AnsibleInventoryType::SymmetricPrivateNodes, true)
185 .inspect_err(|err| {
186 println!("Failed to obtain the inventory of Symmetric private node: {err:?}");
187 })?;
188
189 let symmetric_nat_gateway_inventory = provisioner
190 .ansible_runner
191 .get_inventory(AnsibleInventoryType::SymmetricNatGateway, true)
192 .inspect_err(|err| {
193 println!("Failed to get Symmetric NAT Gateway inventory {err:?}");
194 })?;
195
196 if symmetric_nat_gateway_inventory.len() != symmetric_private_node_vms.len() {
197 println!("The number of Symmetric private nodes does not match the number of Symmetric NAT Gateway VMs");
198 return Err(Error::VmCountMismatch(
199 Some(AnsibleInventoryType::SymmetricPrivateNodes),
200 Some(AnsibleInventoryType::SymmetricNatGateway),
201 ));
202 }
203
204 inventory.symmetric_private_node_vms = symmetric_private_node_vms;
205 inventory.symmetric_nat_gateway_vms = symmetric_nat_gateway_inventory;
206 }
207
208 if should_provision_port_restricted_cone_private_nodes {
209 let port_restricted_cone_private_node_vms = provisioner
210 .ansible_runner
211 .get_inventory(AnsibleInventoryType::PortRestrictedConePrivateNodes, true)
212 .inspect_err(|err| {
213 println!("Failed to get Port Restricted Cone Private Node inventory {err:?}");
214 })?;
215
216 let port_restricted_cone_nat_gateway_inventory = provisioner
217 .ansible_runner
218 .get_inventory(AnsibleInventoryType::PortRestrictedConeNatGateway, true)
219 .inspect_err(|err| {
220 println!("Failed to get Port Restricted Cone NAT Gateway inventory {err:?}");
221 })?;
222
223 if port_restricted_cone_nat_gateway_inventory.len()
224 != port_restricted_cone_private_node_vms.len()
225 {
226 println!("The number of Port Restricted Cone private nodes does not match the number of Port Restricted Cone NAT Gateway VMs");
227 return Err(Error::VmCountMismatch(
228 Some(AnsibleInventoryType::PortRestrictedConePrivateNodes),
229 Some(AnsibleInventoryType::PortRestrictedConeNatGateway),
230 ));
231 }
232
233 inventory.port_restricted_cone_private_node_vms = port_restricted_cone_private_node_vms;
234 inventory.port_restricted_cone_nat_gateway_vms =
235 port_restricted_cone_nat_gateway_inventory;
236 }
237
238 Ok(inventory)
239 }
240
241 pub fn should_provision_full_cone_private_nodes(&self) -> bool {
242 !self.full_cone_private_node_vms.is_empty()
243 }
244
245 pub fn should_provision_symmetric_private_nodes(&self) -> bool {
246 !self.symmetric_private_node_vms.is_empty()
247 }
248
249 pub fn should_provision_port_restricted_cone_private_nodes(&self) -> bool {
250 !self.port_restricted_cone_private_node_vms.is_empty()
251 }
252
253 pub fn symmetric_private_node_and_gateway_map(
254 &self,
255 ) -> Result<HashMap<VirtualMachine, VirtualMachine>> {
256 Self::match_private_node_vm_and_gateway_vm(
257 &self.symmetric_private_node_vms,
258 &self.symmetric_nat_gateway_vms,
259 )
260 }
261
262 pub fn full_cone_private_node_and_gateway_map(
263 &self,
264 ) -> Result<HashMap<VirtualMachine, VirtualMachine>> {
265 Self::match_private_node_vm_and_gateway_vm(
266 &self.full_cone_private_node_vms,
267 &self.full_cone_nat_gateway_vms,
268 )
269 }
270
271 pub fn port_restricted_cone_private_node_and_gateway_map(
272 &self,
273 ) -> Result<HashMap<VirtualMachine, VirtualMachine>> {
274 Self::match_private_node_vm_and_gateway_vm(
275 &self.port_restricted_cone_private_node_vms,
276 &self.port_restricted_cone_nat_gateway_vms,
277 )
278 }
279
280 pub fn match_private_node_vm_and_gateway_vm(
281 private_node_vms: &[VirtualMachine],
282 nat_gateway_vms: &[VirtualMachine],
283 ) -> Result<HashMap<VirtualMachine, VirtualMachine>> {
284 if private_node_vms.len() != nat_gateway_vms.len() {
285 println!(
286 "The number of private node VMs ({}) does not match the number of NAT Gateway VMs ({})",
287 private_node_vms.len(),
288 nat_gateway_vms.len()
289 );
290 error!("The number of private node VMs does not match the number of NAT Gateway VMs: Private VMs: {private_node_vms:?} Nat gateway VMs: {nat_gateway_vms:?}");
291 return Err(Error::VmCountMismatch(None, None));
292 }
293
294 let mut map = HashMap::new();
295 for private_vm in private_node_vms {
296 let nat_gateway = nat_gateway_vms
297 .iter()
298 .find(|vm| {
299 let private_node_name = private_vm.name.split('-').next_back().unwrap();
300 let nat_gateway_name = vm.name.split('-').next_back().unwrap();
301 private_node_name == nat_gateway_name
302 })
303 .ok_or_else(|| {
304 println!(
305 "Failed to find a matching NAT Gateway for private node: {}",
306 private_vm.name
307 );
308 error!("Failed to find a matching NAT Gateway for private node: {}. Private VMs: {private_node_vms:?} Nat gateway VMs: {nat_gateway_vms:?}", private_vm.name);
309 Error::VmCountMismatch(None, None)
310 })?;
311
312 let _ = map.insert(private_vm.clone(), nat_gateway.clone());
313 }
314
315 Ok(map)
316 }
317}
318
319impl From<BootstrapOptions> for ProvisionOptions {
320 fn from(bootstrap_options: BootstrapOptions) -> Self {
321 ProvisionOptions {
322 ant_version: None,
323 binary_option: bootstrap_options.binary_option,
324 chunk_size: bootstrap_options.chunk_size,
325 chunk_tracker_data_addresses: None,
326 chunk_tracker_services: None,
327 client_env_variables: None,
328 delayed_verifier_batch_size: None,
329 disable_nodes: false,
330 delayed_verifier_quorum_value: None,
331 enable_logging: bootstrap_options.enable_logging,
332 enable_metrics: true,
333 evm_data_payments_address: bootstrap_options.evm_data_payments_address,
334 evm_merkle_payments_address: bootstrap_options.evm_merkle_payments_address,
335 evm_network: bootstrap_options.evm_network,
336 evm_payment_token_address: bootstrap_options.evm_payment_token_address,
337 evm_rpc_url: bootstrap_options.evm_rpc_url,
338 expected_hash: None,
339 expected_size: None,
340 file_address: None,
341 full_cone_private_node_count: bootstrap_options.full_cone_private_node_count,
342 funding_wallet_secret_key: None,
343 gas_amount: None,
344 interval: Some(bootstrap_options.interval),
345 log_format: bootstrap_options.log_format,
346 max_archived_log_files: bootstrap_options.max_archived_log_files,
347 max_log_files: bootstrap_options.max_log_files,
348 max_uploads: None,
349 merkle: false,
350 name: bootstrap_options.name,
351 network_id: Some(bootstrap_options.network_id),
352 network_dashboard_branch: None,
353 node_count: bootstrap_options.node_count,
354 node_env_variables: bootstrap_options.node_env_variables,
355 output_inventory_dir_path: bootstrap_options.output_inventory_dir_path,
356 peer_cache_node_count: 0,
357 performance_verifier_batch_size: None,
358 public_rpc: false,
359 random_verifier_batch_size: None,
360 repair_service_count: 0,
361 data_retrieval_service_count: 0,
362 rewards_address: Some(bootstrap_options.rewards_address),
363 scan_frequency: None,
364 sleep_duration: None,
365 single_node_payment: false,
366 start_chunk_trackers: false,
367 start_data_retrieval: false,
368 start_delayed_verifier: false,
369 start_random_verifier: false,
370 start_performance_verifier: false,
371 start_uploaders: false,
372 symmetric_private_node_count: bootstrap_options.symmetric_private_node_count,
373 token_amount: None,
374 upload_batch_size: None,
375 upload_size: None,
376 upload_interval: None,
377 uploaders_count: None,
378 upnp_private_node_count: bootstrap_options.upnp_private_node_count,
379 port_restricted_cone_private_node_count: 0,
380 wallet_secret_keys: None,
381 }
382 }
383}
384
385impl From<DeployOptions> for ProvisionOptions {
386 fn from(deploy_options: DeployOptions) -> Self {
387 ProvisionOptions {
388 ant_version: None,
389 binary_option: deploy_options.binary_option,
390 chunk_size: deploy_options.chunk_size,
391 chunk_tracker_data_addresses: if deploy_options.chunk_tracker_data_addresses.is_empty()
392 {
393 None
394 } else {
395 Some(deploy_options.chunk_tracker_data_addresses)
396 },
397 chunk_tracker_services: Some(deploy_options.chunk_tracker_services),
398 client_env_variables: deploy_options.client_env_variables,
399 delayed_verifier_batch_size: None,
400 disable_nodes: false,
401 delayed_verifier_quorum_value: None,
402 enable_logging: deploy_options.enable_logging,
403 enable_metrics: deploy_options.enable_metrics,
404 node_env_variables: deploy_options.node_env_variables,
405 evm_data_payments_address: deploy_options.evm_data_payments_address,
406 evm_merkle_payments_address: deploy_options.evm_merkle_payments_address,
407 evm_network: deploy_options.evm_network,
408 evm_payment_token_address: deploy_options.evm_payment_token_address,
409 evm_rpc_url: deploy_options.evm_rpc_url,
410 expected_hash: None,
411 expected_size: None,
412 file_address: None,
413 full_cone_private_node_count: deploy_options.full_cone_private_node_count,
414 funding_wallet_secret_key: deploy_options.funding_wallet_secret_key,
415 gas_amount: deploy_options.initial_gas,
416 interval: Some(deploy_options.interval),
417 log_format: deploy_options.log_format,
418 max_archived_log_files: deploy_options.max_archived_log_files,
419 max_log_files: deploy_options.max_log_files,
420 max_uploads: deploy_options.max_uploads,
421 merkle: deploy_options.merkle,
422 name: deploy_options.name,
423 network_id: Some(deploy_options.network_id),
424 network_dashboard_branch: deploy_options.network_dashboard_branch,
425 node_count: deploy_options.node_count,
426 output_inventory_dir_path: deploy_options.output_inventory_dir_path,
427 peer_cache_node_count: deploy_options.peer_cache_node_count,
428 performance_verifier_batch_size: None,
429 port_restricted_cone_private_node_count: deploy_options
430 .port_restricted_cone_private_node_count,
431 public_rpc: deploy_options.public_rpc,
432 random_verifier_batch_size: None,
433 repair_service_count: 0,
434 data_retrieval_service_count: 0,
435 rewards_address: Some(deploy_options.rewards_address),
436 scan_frequency: None,
437 sleep_duration: None,
438 single_node_payment: deploy_options.single_node_payment,
439 start_chunk_trackers: deploy_options.start_chunk_trackers,
440 start_data_retrieval: false,
441 start_delayed_verifier: deploy_options.start_delayed_verifier,
442 start_performance_verifier: deploy_options.start_performance_verifier,
443 start_random_verifier: deploy_options.start_random_verifier,
444 start_uploaders: false,
445 symmetric_private_node_count: deploy_options.symmetric_private_node_count,
446 token_amount: deploy_options.initial_tokens,
447 upload_batch_size: None,
448 upload_size: Some(deploy_options.upload_size),
449 upload_interval: Some(deploy_options.upload_interval),
450 uploaders_count: Some(deploy_options.uploaders_count),
451 upnp_private_node_count: deploy_options.upnp_private_node_count,
452 wallet_secret_keys: None,
453 }
454 }
455}
456
457impl From<ClientsDeployOptions> for ProvisionOptions {
458 fn from(client_options: ClientsDeployOptions) -> Self {
459 Self {
460 ant_version: None,
461 binary_option: client_options.binary_option,
462 chunk_size: client_options.chunk_size,
463 chunk_tracker_data_addresses: if client_options.chunk_tracker_data_addresses.is_empty()
464 {
465 None
466 } else {
467 Some(client_options.chunk_tracker_data_addresses)
468 },
469 chunk_tracker_services: Some(client_options.chunk_tracker_services),
470 client_env_variables: client_options.client_env_variables,
471 delayed_verifier_batch_size: client_options.delayed_verifier_batch_size,
472 disable_nodes: false,
473 delayed_verifier_quorum_value: client_options.delayed_verifier_quorum_value,
474 enable_logging: false,
475 enable_metrics: client_options.enable_metrics,
476 start_random_verifier: client_options.start_random_verifier,
477 start_performance_verifier: client_options.start_performance_verifier,
478 start_uploaders: client_options.start_uploaders,
479 evm_data_payments_address: client_options.evm_details.data_payments_address,
480 evm_merkle_payments_address: client_options.evm_details.merkle_payments_address,
481 evm_network: client_options.evm_details.network,
482 evm_payment_token_address: client_options.evm_details.payment_token_address,
483 evm_rpc_url: client_options.evm_details.rpc_url,
484 expected_hash: client_options.expected_hash,
485 expected_size: client_options.expected_size,
486 file_address: client_options.file_address,
487 full_cone_private_node_count: 0,
488 funding_wallet_secret_key: client_options.funding_wallet_secret_key,
489 gas_amount: client_options.initial_gas,
490 interval: None,
491 log_format: None,
492 max_archived_log_files: client_options.max_archived_log_files,
493 max_log_files: client_options.max_log_files,
494 max_uploads: client_options.max_uploads,
495 merkle: false,
496 name: client_options.name,
497 network_id: client_options.network_id,
498 network_dashboard_branch: None,
499 node_count: 0,
500 node_env_variables: None,
501 output_inventory_dir_path: client_options.output_inventory_dir_path,
502 peer_cache_node_count: 0,
503 performance_verifier_batch_size: client_options.performance_verifier_batch_size,
504 public_rpc: false,
505 random_verifier_batch_size: client_options.random_verifier_batch_size,
506 repair_service_count: client_options.repair_service_count,
507 data_retrieval_service_count: client_options.data_retrieval_service_count,
508 rewards_address: None,
509 scan_frequency: client_options.scan_frequency,
510 sleep_duration: client_options.sleep_duration,
511 single_node_payment: false,
512 start_chunk_trackers: client_options.start_chunk_trackers,
513 start_data_retrieval: client_options.start_data_retrieval,
514 start_delayed_verifier: client_options.start_delayed_verifier,
515 symmetric_private_node_count: 0,
516 token_amount: client_options.initial_tokens,
517 upload_batch_size: client_options.upload_batch_size,
518 upload_size: client_options.upload_size,
519 upload_interval: None,
520 uploaders_count: Some(client_options.uploaders_count),
521 upnp_private_node_count: 0,
522 port_restricted_cone_private_node_count: 0,
523 wallet_secret_keys: client_options.wallet_secret_keys,
524 }
525 }
526}
527
528#[derive(Clone)]
529pub struct AnsibleProvisioner {
530 pub ansible_runner: AnsibleRunner,
531 pub cloud_provider: CloudProvider,
532 pub ssh_client: SshClient,
533}
534
535impl AnsibleProvisioner {
536 pub fn new(
537 ansible_runner: AnsibleRunner,
538 cloud_provider: CloudProvider,
539 ssh_client: SshClient,
540 ) -> Self {
541 Self {
542 ansible_runner,
543 cloud_provider,
544 ssh_client,
545 }
546 }
547
548 pub fn build_autonomi_binaries(
549 &self,
550 options: &ProvisionOptions,
551 binaries_to_build: Option<Vec<String>>,
552 ) -> Result<()> {
553 let start = Instant::now();
554 println!("Obtaining IP address for build VM...");
555 let build_inventory = self
556 .ansible_runner
557 .get_inventory(AnsibleInventoryType::Build, true)?;
558 let build_ip = build_inventory[0].public_ip_addr;
559 self.ssh_client
560 .wait_for_ssh_availability(&build_ip, &self.cloud_provider.get_ssh_user())?;
561
562 println!("Running ansible against build VM...");
563 let base_extra_vars = extra_vars::build_binaries_extra_vars_doc(options)?;
564
565 let extra_vars = if let Some(binaries) = binaries_to_build {
566 let mut build_ant = false;
567 let mut build_antnode = false;
568 let mut build_antctl = false;
569 let mut build_antctld = false;
570
571 for binary in &binaries {
572 match binary.as_str() {
573 "ant" => build_ant = true,
574 "antnode" => build_antnode = true,
575 "antctl" => build_antctl = true,
576 "antctld" => build_antctld = true,
577 _ => return Err(Error::InvalidBinaryName(binary.clone())),
578 }
579 }
580
581 let mut json_value: serde_json::Value = serde_json::from_str(&base_extra_vars)?;
582 if let serde_json::Value::Object(ref mut map) = json_value {
583 map.insert("build_ant".to_string(), serde_json::Value::Bool(build_ant));
584 map.insert(
585 "build_antnode".to_string(),
586 serde_json::Value::Bool(build_antnode),
587 );
588 map.insert(
589 "build_antctl".to_string(),
590 serde_json::Value::Bool(build_antctl),
591 );
592 map.insert(
593 "build_antctld".to_string(),
594 serde_json::Value::Bool(build_antctld),
595 );
596 }
597 json_value.to_string()
598 } else {
599 base_extra_vars
600 };
601
602 self.ansible_runner.run_playbook(
603 AnsiblePlaybook::Build,
604 AnsibleInventoryType::Build,
605 Some(extra_vars),
606 )?;
607 print_duration(start.elapsed());
608 Ok(())
609 }
610
611 pub fn cleanup_node_logs(&self, setup_cron: bool) -> Result<()> {
612 for node_inv_type in AnsibleInventoryType::iter_node_type() {
613 self.ansible_runner.run_playbook(
614 AnsiblePlaybook::CleanupLogs,
615 node_inv_type,
616 Some(format!("{{ \"setup_cron\": \"{setup_cron}\" }}")),
617 )?;
618 }
619
620 Ok(())
621 }
622
623 pub fn copy_logs(&self, name: &str, resources_only: bool) -> Result<()> {
624 for node_inv_type in AnsibleInventoryType::iter_node_type() {
625 self.ansible_runner.run_playbook(
626 AnsiblePlaybook::CopyLogs,
627 node_inv_type,
628 Some(format!(
629 "{{ \"env_name\": \"{name}\", \"resources_only\" : \"{resources_only}\" }}"
630 )),
631 )?;
632 }
633 Ok(())
634 }
635
636 pub fn get_all_node_inventory(&self) -> Result<Vec<VirtualMachine>> {
637 let mut all_node_inventory = Vec::new();
638 for node_inv_type in AnsibleInventoryType::iter_node_type() {
639 all_node_inventory.extend(self.ansible_runner.get_inventory(node_inv_type, false)?);
640 }
641
642 Ok(all_node_inventory)
643 }
644
645 pub fn get_symmetric_nat_gateway_inventory(&self) -> Result<Vec<VirtualMachine>> {
646 self.ansible_runner
647 .get_inventory(AnsibleInventoryType::SymmetricNatGateway, false)
648 }
649
650 pub fn get_full_cone_nat_gateway_inventory(&self) -> Result<Vec<VirtualMachine>> {
651 self.ansible_runner
652 .get_inventory(AnsibleInventoryType::FullConeNatGateway, false)
653 }
654
655 pub fn get_client_inventory(&self) -> Result<Vec<VirtualMachine>> {
656 self.ansible_runner
657 .get_inventory(AnsibleInventoryType::Clients, false)
658 }
659
660 pub async fn get_node_registries(
661 &self,
662 inventory_type: &AnsibleInventoryType,
663 ) -> Result<DeploymentNodeRegistries> {
664 debug!("Fetching node manager inventory for {inventory_type:?}");
665 let temp_dir_path = tempfile::tempdir()?.keep();
666 let temp_dir_json = serde_json::to_string(&temp_dir_path)?;
667
668 self.ansible_runner.run_playbook(
669 AnsiblePlaybook::AntCtlInventory,
670 *inventory_type,
671 Some(format!("{{ \"dest\": {temp_dir_json} }}")),
672 )?;
673
674 let node_registry_paths = WalkDir::new(temp_dir_path)
675 .into_iter()
676 .flatten()
677 .filter_map(|entry| {
678 if entry.file_type().is_file()
679 && entry.path().extension().is_some_and(|ext| ext == "json")
680 {
681 let mut vm_name = entry.path().to_path_buf();
683 trace!("Found file with json extension: {vm_name:?}");
684 vm_name.pop();
685 vm_name.pop();
686 vm_name.pop();
687 trace!("Extracting the vm name from the path");
689 let vm_name = vm_name.file_name()?.to_str()?;
690 trace!("Extracted vm name from path: {vm_name}");
691 Some((vm_name.to_string(), entry.path().to_path_buf()))
692 } else {
693 None
694 }
695 })
696 .collect::<Vec<(String, PathBuf)>>();
697
698 let mut node_registries = Vec::new();
699 let mut failed_vms = Vec::new();
700 for (vm_name, file_path) in node_registry_paths {
701 match NodeRegistryManager::load(&file_path).await {
702 Ok(node_registry) => node_registries.push((vm_name.clone(), node_registry)),
703 Err(_) => failed_vms.push(vm_name.clone()),
704 }
705 }
706
707 let deployment_registries = DeploymentNodeRegistries {
708 inventory_type: *inventory_type,
709 retrieved_registries: node_registries,
710 failed_vms,
711 };
712 Ok(deployment_registries)
713 }
714
715 pub fn provision_evm_nodes(&self, options: &ProvisionOptions) -> Result<()> {
716 let start = Instant::now();
717 println!("Obtaining IP address for EVM nodes...");
718 let evm_node_inventory = self
719 .ansible_runner
720 .get_inventory(AnsibleInventoryType::EvmNodes, true)?;
721 let evm_node_ip = evm_node_inventory[0].public_ip_addr;
722 self.ssh_client
723 .wait_for_ssh_availability(&evm_node_ip, &self.cloud_provider.get_ssh_user())?;
724
725 println!("Running ansible against EVM nodes...");
726 self.ansible_runner.run_playbook(
727 AnsiblePlaybook::EvmNodes,
728 AnsibleInventoryType::EvmNodes,
729 Some(extra_vars::build_evm_nodes_extra_vars_doc(
730 &options.name,
731 &self.cloud_provider,
732 &options.binary_option,
733 )),
734 )?;
735 print_duration(start.elapsed());
736 Ok(())
737 }
738
739 pub fn provision_genesis_node(&self, options: &ProvisionOptions) -> Result<()> {
740 let start = Instant::now();
741 let genesis_inventory = self
742 .ansible_runner
743 .get_inventory(AnsibleInventoryType::Genesis, true)?;
744 let genesis_ip = genesis_inventory[0].public_ip_addr;
745 self.ssh_client
746 .wait_for_ssh_availability(&genesis_ip, &self.cloud_provider.get_ssh_user())?;
747 self.ansible_runner.run_playbook(
748 AnsiblePlaybook::Genesis,
749 AnsibleInventoryType::Genesis,
750 Some(extra_vars::build_node_extra_vars_doc(
751 &self.cloud_provider.to_string(),
752 options,
753 NodeType::Genesis,
754 None,
755 None,
756 1,
757 options.evm_network.clone(),
758 false,
759 )?),
760 )?;
761
762 print_duration(start.elapsed());
763
764 Ok(())
765 }
766
767 pub fn provision_full_cone(
768 &self,
769 options: &ProvisionOptions,
770 initial_contact_peer: Option<String>,
771 initial_network_contacts_url: Option<String>,
772 private_node_inventory: PrivateNodeProvisionInventory,
773 new_full_cone_nat_gateway_new_vms_for_upscale: Option<Vec<VirtualMachine>>,
774 ) -> Result<()> {
775 let start = Instant::now();
777 self.print_ansible_run_banner("Provision Full Cone NAT Gateway - Step 1");
778
779 for vm in new_full_cone_nat_gateway_new_vms_for_upscale
780 .as_ref()
781 .unwrap_or(&private_node_inventory.full_cone_nat_gateway_vms)
782 .iter()
783 {
784 println!(
785 "Checking SSH availability for Full Cone NAT Gateway: {}",
786 vm.public_ip_addr
787 );
788 self.ssh_client
789 .wait_for_ssh_availability(&vm.public_ip_addr, &self.cloud_provider.get_ssh_user())
790 .map_err(|e| {
791 println!("Failed to establish SSH connection to Full Cone NAT Gateway: {e}");
792 e
793 })?;
794 }
795
796 let mut modified_private_node_inventory = private_node_inventory.clone();
797
798 if let Some(new_full_cone_nat_gateway_new_vms_for_upscale) =
800 &new_full_cone_nat_gateway_new_vms_for_upscale
801 {
802 debug!("Removing existing full cone NAT Gateway and private node VMs from the inventory. Old inventory: {modified_private_node_inventory:?}");
803 let mut names_to_keep = Vec::new();
804
805 for vm in new_full_cone_nat_gateway_new_vms_for_upscale.iter() {
806 let nat_gateway_name = vm.name.split('-').next_back().unwrap();
807 names_to_keep.push(nat_gateway_name);
808 }
809
810 modified_private_node_inventory
811 .full_cone_nat_gateway_vms
812 .retain(|vm| {
813 let nat_gateway_name = vm.name.split('-').next_back().unwrap();
814 names_to_keep.contains(&nat_gateway_name)
815 });
816 modified_private_node_inventory
817 .full_cone_private_node_vms
818 .retain(|vm| {
819 let nat_gateway_name = vm.name.split('-').next_back().unwrap();
820 names_to_keep.contains(&nat_gateway_name)
821 });
822 debug!("New inventory after removing existing full cone NAT Gateway and private node VMs: {modified_private_node_inventory:?}");
823 }
824
825 if modified_private_node_inventory
826 .full_cone_nat_gateway_vms
827 .is_empty()
828 {
829 error!("There are no full cone NAT Gateway VMs available to upscale");
830 return Ok(());
831 }
832
833 let private_node_ip_map = modified_private_node_inventory
834 .full_cone_private_node_and_gateway_map()?
835 .into_iter()
836 .map(|(k, v)| {
837 let gateway_name = if new_full_cone_nat_gateway_new_vms_for_upscale.is_some() {
838 debug!("Upscaling, using public IP address for gateway name");
839 v.public_ip_addr.to_string()
840 } else {
841 v.name.clone()
842 };
843 (gateway_name, k.private_ip_addr)
844 })
845 .collect::<HashMap<String, IpAddr>>();
846
847 if private_node_ip_map.is_empty() {
848 println!("There are no full cone private node VM available to be routed through the full cone NAT Gateway");
849 return Err(Error::EmptyInventory(
850 AnsibleInventoryType::FullConePrivateNodes,
851 ));
852 }
853
854 let vars = extra_vars::build_nat_gateway_extra_vars_doc(
855 &options.name,
856 private_node_ip_map.clone(),
857 "step1",
858 );
859 debug!("Provisioning Full Cone NAT Gateway - Step 1 with vars: {vars}");
860 let gateway_inventory = if new_full_cone_nat_gateway_new_vms_for_upscale.is_some() {
861 debug!("Upscaling, using static inventory for full cone nat gateway.");
862 generate_full_cone_nat_gateway_static_environment_inventory(
863 &modified_private_node_inventory.full_cone_nat_gateway_vms,
864 &options.name,
865 &options.output_inventory_dir_path,
866 )?;
867
868 AnsibleInventoryType::FullConeNatGatewayStatic
869 } else {
870 AnsibleInventoryType::FullConeNatGateway
871 };
872 self.ansible_runner.run_playbook(
873 AnsiblePlaybook::StaticFullConeNatGateway,
874 gateway_inventory,
875 Some(vars),
876 )?;
877
878 self.print_ansible_run_banner("Provisioning Full Cone Private Node Config");
880
881 generate_full_cone_private_node_static_environment_inventory(
882 &options.name,
883 &options.output_inventory_dir_path,
884 &private_node_inventory.full_cone_private_node_vms,
885 &private_node_inventory.full_cone_nat_gateway_vms,
886 &self.ssh_client.private_key_path,
887 )
888 .inspect_err(|err| {
889 error!("Failed to generate full cone private node static inv with err: {err:?}")
890 })?;
891
892 println!("Obtaining IP addresses for nodes...");
896 let inventory = self
897 .ansible_runner
898 .get_inventory(AnsibleInventoryType::FullConePrivateNodes, true)?;
899
900 println!("Waiting for SSH availability on Symmetric Private nodes...");
901 for vm in inventory.iter() {
902 println!(
903 "Checking SSH availability for {}: {}",
904 vm.name, vm.public_ip_addr
905 );
906 self.ssh_client
907 .wait_for_ssh_availability(&vm.public_ip_addr, &self.cloud_provider.get_ssh_user())
908 .map_err(|e| {
909 println!("Failed to establish SSH connection to {}: {}", vm.name, e);
910 e
911 })?;
912 }
913
914 println!("SSH is available on all nodes. Proceeding with provisioning...");
915
916 self.ansible_runner.run_playbook(
917 AnsiblePlaybook::PrivateNodeConfig,
918 AnsibleInventoryType::FullConePrivateNodes,
919 Some(
920 extra_vars::build_full_cone_private_node_config_extra_vars_docs(
921 &private_node_inventory,
922 )?,
923 ),
924 )?;
925
926 let vars = extra_vars::build_nat_gateway_extra_vars_doc(
929 &options.name,
930 private_node_ip_map,
931 "step2",
932 );
933
934 self.print_ansible_run_banner("Provisioning Full Cone NAT Gateway - Step 2");
935 debug!("Provisioning Full Cone NAT Gateway - Step 2 with vars: {vars}");
936 self.ansible_runner.run_playbook(
937 AnsiblePlaybook::StaticFullConeNatGateway,
938 gateway_inventory,
939 Some(vars),
940 )?;
941
942 let home_dir = std::env::var("HOME").inspect_err(|err| {
945 println!("Failed to get home directory with error: {err:?}",);
946 })?;
947 let known_hosts_path = format!("{home_dir}/.ssh/known_hosts");
948 debug!("Cleaning up known hosts file at {known_hosts_path} ");
949 run_external_command(
950 PathBuf::from("rm"),
951 std::env::current_dir()?,
952 vec![known_hosts_path],
953 false,
954 false,
955 )?;
956
957 self.print_ansible_run_banner("Provision Full Cone Private Nodes");
958
959 self.ssh_client.set_full_cone_nat_routed_vms(
960 &private_node_inventory.full_cone_private_node_vms,
961 &private_node_inventory.full_cone_nat_gateway_vms,
962 )?;
963
964 self.provision_nodes(
965 options,
966 initial_contact_peer,
967 initial_network_contacts_url,
968 NodeType::FullConePrivateNode,
969 )?;
970
971 print_duration(start.elapsed());
972 Ok(())
973 }
974
975 pub fn provision_port_restricted_cone(
976 &self,
977 options: &ProvisionOptions,
978 initial_contact_peer: Option<String>,
979 initial_network_contacts_url: Option<String>,
980 private_node_inventory: PrivateNodeProvisionInventory,
981 new_port_restricted_cone_nat_gateway_new_vms_for_upscale: Option<Vec<VirtualMachine>>,
982 ) -> Result<()> {
983 let start = Instant::now();
985 self.print_ansible_run_banner("Provision Port Restricted Cone NAT Gateway - Step 1");
986
987 for vm in new_port_restricted_cone_nat_gateway_new_vms_for_upscale
988 .as_ref()
989 .unwrap_or(&private_node_inventory.port_restricted_cone_nat_gateway_vms)
990 .iter()
991 {
992 println!(
993 "Checking SSH availability for Port Restricted Cone NAT Gateway: {}",
994 vm.public_ip_addr
995 );
996 self.ssh_client
997 .wait_for_ssh_availability(&vm.public_ip_addr, &self.cloud_provider.get_ssh_user())
998 .map_err(|e| {
999 println!("Failed to establish SSH connection to Port Restricted Cone NAT Gateway: {e}");
1000 e
1001 })?;
1002 }
1003
1004 let mut modified_private_node_inventory = private_node_inventory.clone();
1005
1006 if let Some(new_port_restricted_cone_nat_gateway_new_vms_for_upscale) =
1008 &new_port_restricted_cone_nat_gateway_new_vms_for_upscale
1009 {
1010 debug!("Removing existing port restricted cone NAT Gateway and private node VMs from the inventory. Old inventory: {modified_private_node_inventory:?}");
1011 let mut names_to_keep = Vec::new();
1012
1013 for vm in new_port_restricted_cone_nat_gateway_new_vms_for_upscale.iter() {
1014 let nat_gateway_name = vm.name.split('-').next_back().unwrap();
1015 names_to_keep.push(nat_gateway_name);
1016 }
1017
1018 modified_private_node_inventory
1019 .port_restricted_cone_nat_gateway_vms
1020 .retain(|vm| {
1021 let nat_gateway_name = vm.name.split('-').next_back().unwrap();
1022 names_to_keep.contains(&nat_gateway_name)
1023 });
1024 modified_private_node_inventory
1025 .port_restricted_cone_private_node_vms
1026 .retain(|vm| {
1027 let nat_gateway_name = vm.name.split('-').next_back().unwrap();
1028 names_to_keep.contains(&nat_gateway_name)
1029 });
1030 debug!("New inventory after removing existing port restricted cone NAT Gateway and private node VMs: {modified_private_node_inventory:?}");
1031 }
1032
1033 if modified_private_node_inventory
1034 .port_restricted_cone_nat_gateway_vms
1035 .is_empty()
1036 {
1037 error!("There are no port restricted cone NAT Gateway VMs available to upscale");
1038 return Ok(());
1039 }
1040
1041 let private_node_ip_map = modified_private_node_inventory
1042 .port_restricted_cone_private_node_and_gateway_map()?
1043 .into_iter()
1044 .map(|(k, v)| {
1045 let gateway_name =
1046 if new_port_restricted_cone_nat_gateway_new_vms_for_upscale.is_some() {
1047 debug!("Upscaling, using public IP address for gateway name");
1048 v.public_ip_addr.to_string()
1049 } else {
1050 v.name.clone()
1051 };
1052 (gateway_name, k.private_ip_addr)
1053 })
1054 .collect::<HashMap<String, IpAddr>>();
1055
1056 if private_node_ip_map.is_empty() {
1057 println!("There are no port restricted cone private node VM available to be routed through the port restricted cone NAT Gateway");
1058 return Err(Error::EmptyInventory(
1059 AnsibleInventoryType::PortRestrictedConePrivateNodes,
1060 ));
1061 }
1062
1063 let vars = extra_vars::build_nat_gateway_extra_vars_doc(
1064 &options.name,
1065 private_node_ip_map.clone(),
1066 "step1",
1067 );
1068 debug!("Provisioning Port Restricted Cone NAT Gateway - Step 1 with vars: {vars}");
1069 let gateway_inventory =
1070 if new_port_restricted_cone_nat_gateway_new_vms_for_upscale.is_some() {
1071 debug!("Upscaling, using static inventory for port restricted cone nat gateway.");
1072 generate_port_restricted_cone_nat_gateway_static_environment_inventory(
1073 &modified_private_node_inventory.port_restricted_cone_nat_gateway_vms,
1074 &options.name,
1075 &options.output_inventory_dir_path,
1076 )?;
1077
1078 AnsibleInventoryType::PortRestrictedConeNatGatewayStatic
1079 } else {
1080 AnsibleInventoryType::PortRestrictedConeNatGateway
1081 };
1082 self.ansible_runner.run_playbook(
1083 AnsiblePlaybook::PortRestrictedConeNatGateway,
1084 gateway_inventory,
1085 Some(vars),
1086 )?;
1087
1088 self.print_ansible_run_banner("Provisioning Port Restricted Cone Private Node Config");
1090
1091 generate_port_restricted_cone_private_node_static_environment_inventory(
1092 &options.name,
1093 &options.output_inventory_dir_path,
1094 &private_node_inventory.port_restricted_cone_private_node_vms,
1095 &private_node_inventory.port_restricted_cone_nat_gateway_vms,
1096 &self.ssh_client.private_key_path,
1097 )
1098 .inspect_err(|err| {
1099 error!(
1100 "Failed to generate port restricted cone private node static inv with err: {err:?}"
1101 )
1102 })?;
1103
1104 println!("Obtaining IP addresses for nodes...");
1108 let inventory = self
1109 .ansible_runner
1110 .get_inventory(AnsibleInventoryType::PortRestrictedConePrivateNodes, true)?;
1111
1112 println!("Waiting for SSH availability on Port Restricted Cone Private nodes...");
1113 for vm in inventory.iter() {
1114 println!(
1115 "Checking SSH availability for {}: {}",
1116 vm.name, vm.public_ip_addr
1117 );
1118 self.ssh_client
1119 .wait_for_ssh_availability(&vm.public_ip_addr, &self.cloud_provider.get_ssh_user())
1120 .map_err(|e| {
1121 println!("Failed to establish SSH connection to {}: {}", vm.name, e);
1122 e
1123 })?;
1124 }
1125
1126 println!("SSH is available on all nodes. Proceeding with provisioning...");
1127
1128 self.ansible_runner.run_playbook(
1129 AnsiblePlaybook::PrivateNodeConfig,
1130 AnsibleInventoryType::PortRestrictedConePrivateNodesStatic,
1131 Some(
1132 extra_vars::build_port_restricted_cone_private_node_config_extra_vars_docs(
1133 &private_node_inventory,
1134 )?,
1135 ),
1136 )?;
1137
1138 let vars = extra_vars::build_nat_gateway_extra_vars_doc(
1141 &options.name,
1142 private_node_ip_map,
1143 "step2",
1144 );
1145
1146 self.print_ansible_run_banner("Provisioning Port Restricted Cone NAT Gateway - Step 2");
1147 debug!("Provisioning Port Restricted Cone NAT Gateway - Step 2 with vars: {vars}");
1148 self.ansible_runner.run_playbook(
1149 AnsiblePlaybook::PortRestrictedConeNatGateway,
1150 gateway_inventory,
1151 Some(vars),
1152 )?;
1153
1154 let home_dir = std::env::var("HOME").inspect_err(|err| {
1157 println!("Failed to get home directory with error: {err:?}",);
1158 })?;
1159 let known_hosts_path = format!("{home_dir}/.ssh/known_hosts");
1160 debug!("Cleaning up known hosts file at {known_hosts_path} ");
1161 run_external_command(
1162 PathBuf::from("rm"),
1163 std::env::current_dir()?,
1164 vec![known_hosts_path],
1165 false,
1166 false,
1167 )?;
1168
1169 self.print_ansible_run_banner("Provision Port Restricted Cone Private Nodes");
1170
1171 self.ssh_client.set_port_restricted_cone_nat_routed_vms(
1172 &private_node_inventory.port_restricted_cone_private_node_vms,
1173 &private_node_inventory.port_restricted_cone_nat_gateway_vms,
1174 )?;
1175
1176 self.provision_nodes(
1177 options,
1178 initial_contact_peer,
1179 initial_network_contacts_url,
1180 NodeType::PortRestrictedConePrivateNode,
1181 )?;
1182
1183 print_duration(start.elapsed());
1184 Ok(())
1185 }
1186
1187 pub fn provision_symmetric_nat_gateway(
1188 &self,
1189 options: &ProvisionOptions,
1190 private_node_inventory: &PrivateNodeProvisionInventory,
1191 ) -> Result<()> {
1192 let start = Instant::now();
1193 for vm in &private_node_inventory.symmetric_nat_gateway_vms {
1194 println!(
1195 "Checking SSH availability for Symmetric NAT Gateway: {}",
1196 vm.public_ip_addr
1197 );
1198 self.ssh_client
1199 .wait_for_ssh_availability(&vm.public_ip_addr, &self.cloud_provider.get_ssh_user())
1200 .map_err(|e| {
1201 println!("Failed to establish SSH connection to Symmetric NAT Gateway: {e}");
1202 e
1203 })?;
1204 }
1205
1206 let private_node_ip_map = private_node_inventory
1207 .symmetric_private_node_and_gateway_map()?
1208 .into_iter()
1209 .map(|(k, v)| (v.name.clone(), k.private_ip_addr))
1210 .collect::<HashMap<String, IpAddr>>();
1211
1212 if private_node_ip_map.is_empty() {
1213 println!("There are no Symmetric private node VM available to be routed through the Symmetric NAT Gateway");
1214 return Err(Error::EmptyInventory(
1215 AnsibleInventoryType::SymmetricPrivateNodes,
1216 ));
1217 }
1218
1219 let vars = extra_vars::build_nat_gateway_extra_vars_doc(
1220 &options.name,
1221 private_node_ip_map,
1222 "symmetric",
1223 );
1224 debug!("Provisioning Symmetric NAT Gateway with vars: {vars}");
1225 self.ansible_runner.run_playbook(
1226 AnsiblePlaybook::SymmetricNatGateway,
1227 AnsibleInventoryType::SymmetricNatGateway,
1228 Some(vars),
1229 )?;
1230
1231 print_duration(start.elapsed());
1232 Ok(())
1233 }
1234
1235 pub fn provision_nodes(
1236 &self,
1237 options: &ProvisionOptions,
1238 initial_contact_peer: Option<String>,
1239 initial_network_contacts_url: Option<String>,
1240 node_type: NodeType,
1241 ) -> Result<()> {
1242 let start = Instant::now();
1243 let mut write_older_cache_files = false;
1244 let (inventory_type, node_count) = match &node_type {
1245 NodeType::FullConePrivateNode => (
1246 node_type.to_ansible_inventory_type(),
1247 options.full_cone_private_node_count,
1248 ),
1249 NodeType::Generic => (node_type.to_ansible_inventory_type(), options.node_count),
1250 NodeType::Genesis => return Err(Error::InvalidNodeType(node_type)),
1251 NodeType::PeerCache => {
1252 write_older_cache_files = true;
1253 (
1254 node_type.to_ansible_inventory_type(),
1255 options.peer_cache_node_count,
1256 )
1257 }
1258 NodeType::SymmetricPrivateNode => (
1259 node_type.to_ansible_inventory_type(),
1260 options.symmetric_private_node_count,
1261 ),
1262 NodeType::Upnp => (
1263 node_type.to_ansible_inventory_type(),
1264 options.upnp_private_node_count,
1265 ),
1266 NodeType::PortRestrictedConePrivateNode => (
1267 node_type.to_ansible_inventory_type(),
1268 options.port_restricted_cone_private_node_count,
1269 ),
1270 };
1271
1272 println!("Obtaining IP addresses for nodes...");
1276 let inventory = self.ansible_runner.get_inventory(inventory_type, true)?;
1277
1278 println!("Waiting for SSH availability on {node_type:?} nodes...");
1279 for vm in inventory.iter() {
1280 println!(
1281 "Checking SSH availability for {}: {}",
1282 vm.name, vm.public_ip_addr
1283 );
1284 self.ssh_client
1285 .wait_for_ssh_availability(&vm.public_ip_addr, &self.cloud_provider.get_ssh_user())
1286 .map_err(|e| {
1287 println!("Failed to establish SSH connection to {}: {}", vm.name, e);
1288 e
1289 })?;
1290 }
1291
1292 println!("SSH is available on all nodes. Proceeding with provisioning...");
1293
1294 let playbook = match node_type {
1295 NodeType::Generic => AnsiblePlaybook::Nodes,
1296 NodeType::PeerCache => AnsiblePlaybook::PeerCacheNodes,
1297 NodeType::FullConePrivateNode => AnsiblePlaybook::Nodes,
1298 NodeType::PortRestrictedConePrivateNode => AnsiblePlaybook::Nodes,
1299 NodeType::SymmetricPrivateNode => AnsiblePlaybook::Nodes,
1300 NodeType::Upnp => AnsiblePlaybook::Upnp,
1301 _ => return Err(Error::InvalidNodeType(node_type.clone())),
1302 };
1303 self.ansible_runner.run_playbook(
1304 playbook,
1305 inventory_type,
1306 Some(extra_vars::build_node_extra_vars_doc(
1307 &self.cloud_provider.to_string(),
1308 options,
1309 node_type.clone(),
1310 initial_contact_peer,
1311 initial_network_contacts_url,
1312 node_count,
1313 options.evm_network.clone(),
1314 write_older_cache_files,
1315 )?),
1316 )?;
1317
1318 print_duration(start.elapsed());
1319 Ok(())
1320 }
1321
1322 pub fn provision_symmetric_private_nodes(
1323 &self,
1324 options: &mut ProvisionOptions,
1325 initial_contact_peer: Option<String>,
1326 initial_network_contacts_url: Option<String>,
1327 private_node_inventory: &PrivateNodeProvisionInventory,
1328 ) -> Result<()> {
1329 let start = Instant::now();
1330 self.print_ansible_run_banner("Provision Symmetric Private Node Config");
1331
1332 generate_symmetric_private_node_static_environment_inventory(
1333 &options.name,
1334 &options.output_inventory_dir_path,
1335 &private_node_inventory.symmetric_private_node_vms,
1336 &private_node_inventory.symmetric_nat_gateway_vms,
1337 &self.ssh_client.private_key_path,
1338 )
1339 .inspect_err(|err| {
1340 error!("Failed to generate symmetric private node static inv with err: {err:?}")
1341 })?;
1342
1343 self.ssh_client.set_symmetric_nat_routed_vms(
1344 &private_node_inventory.symmetric_private_node_vms,
1345 &private_node_inventory.symmetric_nat_gateway_vms,
1346 )?;
1347
1348 let inventory_type = AnsibleInventoryType::SymmetricPrivateNodes;
1349
1350 println!("Obtaining IP addresses for nodes...");
1354 let inventory = self.ansible_runner.get_inventory(inventory_type, true)?;
1355
1356 println!("Waiting for SSH availability on Symmetric Private nodes...");
1357 for vm in inventory.iter() {
1358 println!(
1359 "Checking SSH availability for {}: {}",
1360 vm.name, vm.public_ip_addr
1361 );
1362 self.ssh_client
1363 .wait_for_ssh_availability(&vm.public_ip_addr, &self.cloud_provider.get_ssh_user())
1364 .map_err(|e| {
1365 println!("Failed to establish SSH connection to {}: {}", vm.name, e);
1366 e
1367 })?;
1368 }
1369
1370 println!("SSH is available on all nodes. Proceeding with provisioning...");
1371
1372 self.ansible_runner.run_playbook(
1373 AnsiblePlaybook::PrivateNodeConfig,
1374 inventory_type,
1375 Some(
1376 extra_vars::build_symmetric_private_node_config_extra_vars_doc(
1377 private_node_inventory,
1378 )?,
1379 ),
1380 )?;
1381
1382 println!("Provisioned Symmetric Private Node Config");
1383 print_duration(start.elapsed());
1384
1385 self.provision_nodes(
1386 options,
1387 initial_contact_peer,
1388 initial_network_contacts_url,
1389 NodeType::SymmetricPrivateNode,
1390 )?;
1391
1392 Ok(())
1393 }
1394
1395 pub async fn provision_downloaders(
1396 &self,
1397 options: &ProvisionOptions,
1398 genesis_multiaddr: Option<String>,
1399 genesis_network_contacts_url: Option<String>,
1400 ) -> Result<()> {
1401 let start = Instant::now();
1402
1403 println!("Running ansible against Client machine to start the downloader script.");
1404 debug!("Running ansible against Client machine to start the downloader script.");
1405
1406 self.ansible_runner.run_playbook(
1407 AnsiblePlaybook::Downloaders,
1408 AnsibleInventoryType::Clients,
1409 Some(extra_vars::build_downloaders_extra_vars_doc(
1410 &self.cloud_provider.to_string(),
1411 options,
1412 genesis_multiaddr,
1413 genesis_network_contacts_url,
1414 )?),
1415 )?;
1416 print_duration(start.elapsed());
1417 Ok(())
1418 }
1419
1420 pub async fn provision_static_downloaders(
1421 &self,
1422 options: &ProvisionOptions,
1423 genesis_multiaddr: Option<String>,
1424 genesis_network_contacts_url: Option<String>,
1425 ) -> Result<()> {
1426 let start = Instant::now();
1427
1428 println!("Running ansible against client machine to start the static downloaders.");
1429 debug!("Running ansible against client machine to start the static downloaders.");
1430
1431 self.ansible_runner.run_playbook(
1432 AnsiblePlaybook::StaticDownloaders,
1433 AnsibleInventoryType::Clients,
1434 Some(extra_vars::build_downloaders_extra_vars_doc(
1435 &self.cloud_provider.to_string(),
1436 options,
1437 genesis_multiaddr,
1438 genesis_network_contacts_url,
1439 )?),
1440 )?;
1441 print_duration(start.elapsed());
1442 Ok(())
1443 }
1444
1445 pub async fn provision_static_uploader(
1446 &self,
1447 options: &ProvisionOptions,
1448 genesis_multiaddr: Option<String>,
1449 genesis_network_contacts_url: Option<String>,
1450 ) -> Result<()> {
1451 let start = Instant::now();
1452
1453 println!("Running ansible against client machine to provision the static uploader");
1454 debug!("Running ansible against client machine to provision the static uploader");
1455
1456 let sk_map = if let Some(wallet_keys) = &options.wallet_secret_keys {
1457 self.prepare_pre_funded_wallets(wallet_keys).await?
1458 } else {
1459 self.deposit_funds_to_clients(&FundingOptions {
1460 evm_data_payments_address: options.evm_data_payments_address.clone(),
1461 evm_merkle_payments_address: options.evm_merkle_payments_address.clone(),
1462 evm_network: options.evm_network.clone(),
1463 evm_payment_token_address: options.evm_payment_token_address.clone(),
1464 evm_rpc_url: options.evm_rpc_url.clone(),
1465 funding_wallet_secret_key: options.funding_wallet_secret_key.clone(),
1466 gas_amount: options.gas_amount,
1467 token_amount: options.token_amount,
1468 uploaders_count: Some(1),
1469 })
1470 .await?
1471 };
1472
1473 let client_vms: Vec<_> = sk_map.keys().cloned().collect();
1474 self.ansible_runner.run_playbook(
1475 AnsiblePlaybook::StaticUploader,
1476 AnsibleInventoryType::Clients,
1477 Some(extra_vars::build_clients_extra_vars_doc(
1478 &self.cloud_provider.to_string(),
1479 options,
1480 genesis_multiaddr,
1481 genesis_network_contacts_url,
1482 &sk_map,
1483 &client_vms,
1484 )?),
1485 )?;
1486 print_duration(start.elapsed());
1487 Ok(())
1488 }
1489
1490 pub async fn provision_uploaders(
1491 &self,
1492 options: &ProvisionOptions,
1493 genesis_multiaddr: Option<String>,
1494 genesis_network_contacts_url: Option<String>,
1495 ) -> Result<()> {
1496 let start = Instant::now();
1497
1498 let sk_map = if let Some(wallet_keys) = &options.wallet_secret_keys {
1499 self.prepare_pre_funded_wallets(wallet_keys).await?
1500 } else {
1501 self.deposit_funds_to_clients(&FundingOptions {
1502 evm_data_payments_address: options.evm_data_payments_address.clone(),
1503 evm_merkle_payments_address: options.evm_merkle_payments_address.clone(),
1504 evm_network: options.evm_network.clone(),
1505 evm_payment_token_address: options.evm_payment_token_address.clone(),
1506 evm_rpc_url: options.evm_rpc_url.clone(),
1507 funding_wallet_secret_key: options.funding_wallet_secret_key.clone(),
1508 gas_amount: options.gas_amount,
1509 token_amount: options.token_amount,
1510 uploaders_count: options.uploaders_count,
1511 })
1512 .await?
1513 };
1514
1515 let client_vms: Vec<_> = sk_map.keys().cloned().collect();
1516 self.ansible_runner.run_playbook(
1517 AnsiblePlaybook::Uploaders,
1518 AnsibleInventoryType::Clients,
1519 Some(extra_vars::build_clients_extra_vars_doc(
1520 &self.cloud_provider.to_string(),
1521 options,
1522 genesis_multiaddr,
1523 genesis_network_contacts_url,
1524 &sk_map,
1525 &client_vms,
1526 )?),
1527 )?;
1528 print_duration(start.elapsed());
1529 Ok(())
1530 }
1531
1532 pub async fn provision_chunk_trackers(
1533 &self,
1534 options: &ProvisionOptions,
1535 genesis_multiaddr: Option<String>,
1536 genesis_network_contacts_url: Option<String>,
1537 ) -> Result<()> {
1538 let start = Instant::now();
1539
1540 let client_vms = self
1541 .ansible_runner
1542 .get_inventory(AnsibleInventoryType::Clients, true)?;
1543
1544 self.ansible_runner.run_playbook(
1545 AnsiblePlaybook::ChunkTrackers,
1546 AnsibleInventoryType::Clients,
1547 Some(extra_vars::build_clients_extra_vars_doc(
1548 &self.cloud_provider.to_string(),
1549 options,
1550 genesis_multiaddr,
1551 genesis_network_contacts_url,
1552 &HashMap::new(), &client_vms,
1554 )?),
1555 )?;
1556 print_duration(start.elapsed());
1557 Ok(())
1558 }
1559
1560 pub async fn provision_data_retrieval(
1561 &self,
1562 options: &ProvisionOptions,
1563 genesis_network_contacts_url: Option<String>,
1564 ) -> Result<()> {
1565 let start = Instant::now();
1566
1567 self.ansible_runner.run_playbook(
1568 AnsiblePlaybook::DataRetrieval,
1569 AnsibleInventoryType::Clients,
1570 Some(extra_vars::build_data_retrieval_extra_vars_doc(
1571 &self.cloud_provider.to_string(),
1572 options,
1573 genesis_network_contacts_url,
1574 )?),
1575 )?;
1576 print_duration(start.elapsed());
1577 Ok(())
1578 }
1579
1580 pub async fn provision_repair_files(&self, options: &ProvisionOptions) -> Result<()> {
1581 let start = Instant::now();
1582
1583 let client_vms = self
1584 .ansible_runner
1585 .get_inventory(AnsibleInventoryType::Clients, true)?;
1586 let sk_map = self
1587 .prepare_pre_funded_wallets(
1588 &options
1589 .wallet_secret_keys
1590 .clone()
1591 .ok_or_else(|| Error::RepairWalletAddressNotProvided)?,
1592 )
1593 .await?;
1594
1595 self.ansible_runner.run_playbook(
1596 AnsiblePlaybook::RepairFiles,
1597 AnsibleInventoryType::Clients,
1598 Some(extra_vars::build_clients_extra_vars_doc(
1599 &self.cloud_provider.to_string(),
1600 options,
1601 None,
1602 None,
1603 &sk_map,
1604 &client_vms,
1605 )?),
1606 )?;
1607 print_duration(start.elapsed());
1608 Ok(())
1609 }
1610
1611 pub async fn provision_scan_repair(&self, options: &ProvisionOptions) -> Result<()> {
1612 let start = Instant::now();
1613
1614 let client_vms = self
1615 .ansible_runner
1616 .get_inventory(AnsibleInventoryType::Clients, true)?;
1617 let sk_map = self
1618 .prepare_pre_funded_wallets(
1619 &options
1620 .wallet_secret_keys
1621 .clone()
1622 .ok_or_else(|| Error::RepairWalletAddressNotProvided)?,
1623 )
1624 .await?;
1625
1626 self.ansible_runner.run_playbook(
1627 AnsiblePlaybook::ScanRepair,
1628 AnsibleInventoryType::Clients,
1629 Some(extra_vars::build_clients_extra_vars_doc(
1630 &self.cloud_provider.to_string(),
1631 options,
1632 None,
1633 None,
1634 &sk_map,
1635 &client_vms,
1636 )?),
1637 )?;
1638 print_duration(start.elapsed());
1639 Ok(())
1640 }
1641
1642 pub fn start_nodes(
1643 &self,
1644 environment_name: &str,
1645 interval: Duration,
1646 node_type: Option<NodeType>,
1647 custom_inventory: Option<Vec<VirtualMachine>>,
1648 ) -> Result<()> {
1649 let mut extra_vars = ExtraVarsDocBuilder::default();
1650 extra_vars.add_variable("interval", &interval.as_millis().to_string());
1651
1652 if let Some(node_type) = node_type {
1653 println!("Running the start nodes playbook for {node_type:?} nodes");
1654 self.ansible_runner.run_playbook(
1655 AnsiblePlaybook::StartNodes,
1656 node_type.to_ansible_inventory_type(),
1657 Some(extra_vars.build()),
1658 )?;
1659 return Ok(());
1660 }
1661
1662 if let Some(custom_inventory) = custom_inventory {
1663 println!("Running the start nodes playbook with a custom inventory");
1664 generate_custom_environment_inventory(
1665 &custom_inventory,
1666 environment_name,
1667 &self.ansible_runner.working_directory_path.join("inventory"),
1668 )?;
1669 self.ansible_runner.run_playbook(
1670 AnsiblePlaybook::StartNodes,
1671 AnsibleInventoryType::Custom,
1672 Some(extra_vars.build()),
1673 )?;
1674 return Ok(());
1675 }
1676
1677 println!("Running the start nodes playbook for all node types");
1678 for node_inv_type in AnsibleInventoryType::iter_node_type() {
1679 self.ansible_runner.run_playbook(
1680 AnsiblePlaybook::StartNodes,
1681 node_inv_type,
1682 Some(extra_vars.build()),
1683 )?;
1684 }
1685 Ok(())
1686 }
1687
1688 pub fn apply_delete_node_records_cron(
1689 &self,
1690 environment_name: &str,
1691 node_type: Option<NodeType>,
1692 custom_inventory: Option<Vec<VirtualMachine>>,
1693 ) -> Result<()> {
1694 if let Some(node_type) = node_type {
1695 println!("Applying delete node records cron for {node_type:?} nodes");
1696 self.ansible_runner.run_playbook(
1697 AnsiblePlaybook::ApplyDeleteNodeRecordsCron,
1698 node_type.to_ansible_inventory_type(),
1699 None,
1700 )?;
1701 return Ok(());
1702 }
1703
1704 if let Some(custom_inventory) = custom_inventory {
1705 println!("Applying delete node records cron with a custom inventory");
1706 generate_custom_environment_inventory(
1707 &custom_inventory,
1708 environment_name,
1709 &self.ansible_runner.working_directory_path.join("inventory"),
1710 )?;
1711 self.ansible_runner.run_playbook(
1712 AnsiblePlaybook::ApplyDeleteNodeRecordsCron,
1713 AnsibleInventoryType::Custom,
1714 None,
1715 )?;
1716 return Ok(());
1717 }
1718
1719 println!("Applying delete node records cron for all node types");
1720 for node_inv_type in AnsibleInventoryType::iter_node_type() {
1721 self.ansible_runner.run_playbook(
1722 AnsiblePlaybook::ApplyDeleteNodeRecordsCron,
1723 node_inv_type,
1724 None,
1725 )?;
1726 }
1727 Ok(())
1728 }
1729
1730 pub fn reset_nodes(
1731 &self,
1732 environment_name: &str,
1733 node_type: Option<NodeType>,
1734 custom_inventory: Option<Vec<VirtualMachine>>,
1735 ) -> Result<()> {
1736 if let Some(node_type) = node_type {
1737 println!("Running the reset nodes playbook for {node_type:?} nodes");
1738 self.ansible_runner.run_playbook(
1739 AnsiblePlaybook::ResetNodes,
1740 node_type.to_ansible_inventory_type(),
1741 None,
1742 )?;
1743 return Ok(());
1744 }
1745
1746 if let Some(custom_inventory) = custom_inventory {
1747 println!("Running the reset nodes playbook with a custom inventory");
1748 generate_custom_environment_inventory(
1749 &custom_inventory,
1750 environment_name,
1751 &self.ansible_runner.working_directory_path.join("inventory"),
1752 )?;
1753 self.ansible_runner.run_playbook(
1754 AnsiblePlaybook::ResetNodes,
1755 AnsibleInventoryType::Custom,
1756 None,
1757 )?;
1758 return Ok(());
1759 }
1760
1761 println!("Running the reset nodes playbook for all node types");
1762 for node_inv_type in AnsibleInventoryType::iter_node_type() {
1763 self.ansible_runner
1764 .run_playbook(AnsiblePlaybook::ResetNodes, node_inv_type, None)?;
1765 }
1766 Ok(())
1767 }
1768
1769 pub fn status(&self) -> Result<()> {
1770 for node_inv_type in AnsibleInventoryType::iter_node_type() {
1771 self.ansible_runner
1772 .run_playbook(AnsiblePlaybook::Status, node_inv_type, None)?;
1773 }
1774 Ok(())
1775 }
1776
1777 pub fn start_telegraf(
1778 &self,
1779 environment_name: &str,
1780 node_type: Option<NodeType>,
1781 custom_inventory: Option<Vec<VirtualMachine>>,
1782 ) -> Result<()> {
1783 if let Some(node_type) = node_type {
1784 println!("Running the start telegraf playbook for {node_type:?} nodes");
1785 self.ansible_runner.run_playbook(
1786 AnsiblePlaybook::StartTelegraf,
1787 node_type.to_ansible_inventory_type(),
1788 None,
1789 )?;
1790 return Ok(());
1791 }
1792
1793 if let Some(custom_inventory) = custom_inventory {
1794 println!("Running the start telegraf playbook with a custom inventory");
1795 generate_custom_environment_inventory(
1796 &custom_inventory,
1797 environment_name,
1798 &self.ansible_runner.working_directory_path.join("inventory"),
1799 )?;
1800 self.ansible_runner.run_playbook(
1801 AnsiblePlaybook::StartTelegraf,
1802 AnsibleInventoryType::Custom,
1803 None,
1804 )?;
1805 return Ok(());
1806 }
1807
1808 println!("Running the start telegraf playbook for all node types");
1809 for node_inv_type in AnsibleInventoryType::iter_node_type() {
1810 self.ansible_runner.run_playbook(
1811 AnsiblePlaybook::StartTelegraf,
1812 node_inv_type,
1813 None,
1814 )?;
1815 }
1816
1817 Ok(())
1818 }
1819
1820 pub fn stop_nodes(
1821 &self,
1822 environment_name: &str,
1823 interval: Duration,
1824 node_type: Option<NodeType>,
1825 custom_inventory: Option<Vec<VirtualMachine>>,
1826 delay: Option<u64>,
1827 service_names: Option<Vec<String>>,
1828 ) -> Result<()> {
1829 let mut extra_vars = ExtraVarsDocBuilder::default();
1830 extra_vars.add_variable("interval", &interval.as_millis().to_string());
1831 if let Some(delay) = delay {
1832 extra_vars.add_variable("delay", &delay.to_string());
1833 }
1834 if let Some(service_names) = service_names {
1835 extra_vars.add_list_variable("service_names", service_names);
1836 }
1837 let extra_vars = extra_vars.build();
1838
1839 if let Some(node_type) = node_type {
1840 println!("Running the stop nodes playbook for {node_type:?} nodes");
1841 self.ansible_runner.run_playbook(
1842 AnsiblePlaybook::StopNodes,
1843 node_type.to_ansible_inventory_type(),
1844 Some(extra_vars),
1845 )?;
1846 return Ok(());
1847 }
1848
1849 if let Some(custom_inventory) = custom_inventory {
1850 println!("Running the stop nodes playbook with a custom inventory");
1851 generate_custom_environment_inventory(
1852 &custom_inventory,
1853 environment_name,
1854 &self.ansible_runner.working_directory_path.join("inventory"),
1855 )?;
1856 self.ansible_runner.run_playbook(
1857 AnsiblePlaybook::StopNodes,
1858 AnsibleInventoryType::Custom,
1859 Some(extra_vars),
1860 )?;
1861 return Ok(());
1862 }
1863
1864 println!("Running the stop nodes playbook for all node types");
1865 for node_inv_type in AnsibleInventoryType::iter_node_type() {
1866 self.ansible_runner.run_playbook(
1867 AnsiblePlaybook::StopNodes,
1868 node_inv_type,
1869 Some(extra_vars.clone()),
1870 )?;
1871 }
1872
1873 Ok(())
1874 }
1875
1876 pub fn stop_telegraf(
1877 &self,
1878 environment_name: &str,
1879 node_type: Option<NodeType>,
1880 custom_inventory: Option<Vec<VirtualMachine>>,
1881 ) -> Result<()> {
1882 if let Some(node_type) = node_type {
1883 println!("Running the stop telegraf playbook for {node_type:?} nodes");
1884 self.ansible_runner.run_playbook(
1885 AnsiblePlaybook::StopTelegraf,
1886 node_type.to_ansible_inventory_type(),
1887 None,
1888 )?;
1889 return Ok(());
1890 }
1891
1892 if let Some(custom_inventory) = custom_inventory {
1893 println!("Running the stop telegraf playbook with a custom inventory");
1894 generate_custom_environment_inventory(
1895 &custom_inventory,
1896 environment_name,
1897 &self.ansible_runner.working_directory_path.join("inventory"),
1898 )?;
1899 self.ansible_runner.run_playbook(
1900 AnsiblePlaybook::StopTelegraf,
1901 AnsibleInventoryType::Custom,
1902 None,
1903 )?;
1904 return Ok(());
1905 }
1906
1907 println!("Running the stop telegraf playbook for all node types");
1908 for node_inv_type in AnsibleInventoryType::iter_node_type() {
1909 self.ansible_runner
1910 .run_playbook(AnsiblePlaybook::StopTelegraf, node_inv_type, None)?;
1911 }
1912
1913 Ok(())
1914 }
1915
1916 pub fn upgrade_node_telegraf(&self, name: &str) -> Result<()> {
1917 self.ansible_runner.run_playbook(
1918 AnsiblePlaybook::UpgradeNodeTelegrafConfig,
1919 AnsibleInventoryType::PeerCacheNodes,
1920 Some(extra_vars::build_node_telegraf_upgrade(
1921 name,
1922 &NodeType::PeerCache,
1923 )?),
1924 )?;
1925 self.ansible_runner.run_playbook(
1926 AnsiblePlaybook::UpgradeNodeTelegrafConfig,
1927 AnsibleInventoryType::Nodes,
1928 Some(extra_vars::build_node_telegraf_upgrade(
1929 name,
1930 &NodeType::Generic,
1931 )?),
1932 )?;
1933
1934 self.ansible_runner.run_playbook(
1935 AnsiblePlaybook::UpgradeNodeTelegrafConfig,
1936 AnsibleInventoryType::SymmetricPrivateNodes,
1937 Some(extra_vars::build_node_telegraf_upgrade(
1938 name,
1939 &NodeType::SymmetricPrivateNode,
1940 )?),
1941 )?;
1942
1943 self.ansible_runner.run_playbook(
1944 AnsiblePlaybook::UpgradeNodeTelegrafConfig,
1945 AnsibleInventoryType::FullConePrivateNodes,
1946 Some(extra_vars::build_node_telegraf_upgrade(
1947 name,
1948 &NodeType::FullConePrivateNode,
1949 )?),
1950 )?;
1951 Ok(())
1952 }
1953
1954 pub fn upgrade_client_telegraf(&self, name: &str) -> Result<()> {
1955 self.ansible_runner.run_playbook(
1956 AnsiblePlaybook::UpgradeClientTelegrafConfig,
1957 AnsibleInventoryType::Clients,
1958 Some(extra_vars::build_client_telegraf_upgrade(name)?),
1959 )?;
1960 Ok(())
1961 }
1962
1963 pub fn upgrade_nodes(&self, options: &UpgradeOptions) -> Result<()> {
1964 if let Some(custom_inventory) = &options.custom_inventory {
1965 println!("Running the UpgradeNodes with a custom inventory");
1966 generate_custom_environment_inventory(
1967 custom_inventory,
1968 &options.name,
1969 &self.ansible_runner.working_directory_path.join("inventory"),
1970 )?;
1971 match self.ansible_runner.run_playbook(
1972 AnsiblePlaybook::UpgradeNodes,
1973 AnsibleInventoryType::Custom,
1974 Some(options.get_ansible_vars()),
1975 ) {
1976 Ok(()) => println!("All nodes were successfully upgraded"),
1977 Err(_) => {
1978 println!("WARNING: some nodes may not have been upgraded or restarted");
1979 }
1980 }
1981 return Ok(());
1982 }
1983
1984 if let Some(node_type) = &options.node_type {
1985 println!("Running the UpgradeNodes playbook for {node_type:?} nodes");
1986 match self.ansible_runner.run_playbook(
1987 AnsiblePlaybook::UpgradeNodes,
1988 node_type.to_ansible_inventory_type(),
1989 Some(options.get_ansible_vars()),
1990 ) {
1991 Ok(()) => println!("All {node_type:?} nodes were successfully upgraded"),
1992 Err(_) => {
1993 println!(
1994 "WARNING: some {node_type:?} nodes may not have been upgraded or restarted"
1995 );
1996 }
1997 }
1998 return Ok(());
1999 }
2000
2001 println!("Running the UpgradeNodes playbook for all node types");
2002
2003 match self.ansible_runner.run_playbook(
2004 AnsiblePlaybook::UpgradeNodes,
2005 AnsibleInventoryType::PeerCacheNodes,
2006 Some(options.get_ansible_vars()),
2007 ) {
2008 Ok(()) => println!("All Peer Cache nodes were successfully upgraded"),
2009 Err(_) => {
2010 println!("WARNING: some Peer Cacche nodes may not have been upgraded or restarted");
2011 }
2012 }
2013 match self.ansible_runner.run_playbook(
2014 AnsiblePlaybook::UpgradeNodes,
2015 AnsibleInventoryType::Nodes,
2016 Some(options.get_ansible_vars()),
2017 ) {
2018 Ok(()) => println!("All generic nodes were successfully upgraded"),
2019 Err(_) => {
2020 println!("WARNING: some nodes may not have been upgraded or restarted");
2021 }
2022 }
2023 match self.ansible_runner.run_playbook(
2024 AnsiblePlaybook::UpgradeNodes,
2025 AnsibleInventoryType::SymmetricPrivateNodes,
2026 Some(options.get_ansible_vars()),
2027 ) {
2028 Ok(()) => println!("All private nodes were successfully upgraded"),
2029 Err(_) => {
2030 println!("WARNING: some nodes may not have been upgraded or restarted");
2031 }
2032 }
2033 match self.ansible_runner.run_playbook(
2035 AnsiblePlaybook::UpgradeNodes,
2036 AnsibleInventoryType::Genesis,
2037 Some(options.get_ansible_vars()),
2038 ) {
2039 Ok(()) => println!("The genesis nodes was successfully upgraded"),
2040 Err(_) => {
2041 println!("WARNING: the genesis node may not have been upgraded or restarted");
2042 }
2043 }
2044 Ok(())
2045 }
2046
2047 pub fn upgrade_antctl(
2048 &self,
2049 environment_name: &str,
2050 version: &Version,
2051 node_type: Option<NodeType>,
2052 custom_inventory: Option<Vec<VirtualMachine>>,
2053 ) -> Result<()> {
2054 let mut extra_vars = ExtraVarsDocBuilder::default();
2055 extra_vars.add_variable("version", &version.to_string());
2056
2057 if let Some(node_type) = node_type {
2058 println!("Running the upgrade safenode-manager playbook for {node_type:?} nodes");
2059 self.ansible_runner.run_playbook(
2060 AnsiblePlaybook::UpgradeAntctl,
2061 node_type.to_ansible_inventory_type(),
2062 Some(extra_vars.build()),
2063 )?;
2064 return Ok(());
2065 }
2066
2067 if let Some(custom_inventory) = custom_inventory {
2068 println!("Running the upgrade safenode-manager playbook with a custom inventory");
2069 generate_custom_environment_inventory(
2070 &custom_inventory,
2071 environment_name,
2072 &self.ansible_runner.working_directory_path.join("inventory"),
2073 )?;
2074 self.ansible_runner.run_playbook(
2075 AnsiblePlaybook::UpgradeAntctl,
2076 AnsibleInventoryType::Custom,
2077 Some(extra_vars.build()),
2078 )?;
2079 return Ok(());
2080 }
2081
2082 println!("Running the upgrade safenode-manager playbook for all node types");
2083 for node_inv_type in AnsibleInventoryType::iter_node_type() {
2084 self.ansible_runner.run_playbook(
2085 AnsiblePlaybook::UpgradeAntctl,
2086 node_inv_type,
2087 Some(extra_vars.build()),
2088 )?;
2089 }
2090
2091 Ok(())
2092 }
2093
2094 pub fn upgrade_nginx_config(
2095 &self,
2096 environment_name: &str,
2097 custom_inventory: Option<Vec<VirtualMachine>>,
2098 ) -> Result<()> {
2099 if let Some(custom_inventory) = custom_inventory {
2100 println!("Running the upgrade nginx config playbook with a custom inventory");
2101 generate_custom_environment_inventory(
2102 &custom_inventory,
2103 environment_name,
2104 &self.ansible_runner.working_directory_path.join("inventory"),
2105 )?;
2106 self.ansible_runner.run_playbook(
2107 AnsiblePlaybook::UpgradeNginx,
2108 AnsibleInventoryType::Custom,
2109 None,
2110 )?;
2111 return Ok(());
2112 }
2113
2114 println!("Running the upgrade nginx config playbook for peer cache nodes");
2115 self.ansible_runner.run_playbook(
2116 AnsiblePlaybook::UpgradeNginx,
2117 AnsibleInventoryType::PeerCacheNodes,
2118 None,
2119 )?;
2120 Ok(())
2121 }
2122
2123 pub fn upgrade_geoip_telegraf(&self, name: &str) -> Result<()> {
2124 self.ansible_runner.run_playbook(
2125 AnsiblePlaybook::UpgradeGeoIpTelegrafConfig,
2126 AnsibleInventoryType::PeerCacheNodes,
2127 Some(extra_vars::build_node_telegraf_upgrade(
2128 name,
2129 &NodeType::PeerCache,
2130 )?),
2131 )?;
2132 Ok(())
2133 }
2134
2135 pub fn print_ansible_run_banner(&self, s: &str) {
2136 let ansible_run_msg = "Ansible Run: ";
2137 let line = "=".repeat(s.len() + ansible_run_msg.len());
2138 println!("{line}\n{ansible_run_msg}{s}\n{line}");
2139 }
2140}