1use std::{path::PathBuf, time::Duration};
8
9use crate::{
10 ansible::provisioning::{PrivateNodeProvisionInventory, ProvisionOptions},
11 error::Result,
12 write_environment_details, BinaryOption, DeploymentType, EnvironmentDetails, EnvironmentType,
13 EvmDetails, EvmNetwork, InfraRunOptions, LogFormat, NodeType, TestnetDeployer,
14};
15use colored::Colorize;
16use log::error;
17
18#[derive(Clone)]
19pub struct BootstrapOptions {
20 pub binary_option: BinaryOption,
21 pub chunk_size: Option<u64>,
22 pub enable_logging: bool,
23 pub environment_type: EnvironmentType,
24 pub evm_data_payments_address: Option<String>,
25 pub evm_network: EvmNetwork,
26 pub evm_payment_token_address: Option<String>,
27 pub evm_rpc_url: Option<String>,
28 pub full_cone_private_node_count: u16,
29 pub full_cone_private_node_vm_count: Option<u16>,
30 pub full_cone_private_node_volume_size: Option<u16>,
31 pub interval: Duration,
32 pub log_format: Option<LogFormat>,
33 pub max_archived_log_files: u16,
34 pub max_log_files: u16,
35 pub name: String,
36 pub network_contacts_url: Option<String>,
37 pub network_id: u8,
38 pub node_count: u16,
39 pub node_env_variables: Option<Vec<(String, String)>>,
40 pub node_vm_count: Option<u16>,
41 pub node_vm_size: Option<String>,
42 pub node_volume_size: Option<u16>,
43 pub output_inventory_dir_path: PathBuf,
44 pub peer: Option<String>,
45 pub region: String,
46 pub rewards_address: String,
47 pub symmetric_private_node_count: u16,
48 pub symmetric_private_node_vm_count: Option<u16>,
49 pub symmetric_private_node_volume_size: Option<u16>,
50 pub upnp_private_node_count: u16,
51 pub upnp_private_node_vm_count: Option<u16>,
52 pub upnp_private_node_volume_size: Option<u16>,
53}
54
55impl TestnetDeployer {
56 pub async fn bootstrap(&self, options: &BootstrapOptions) -> Result<()> {
57 let build_custom_binaries = options.binary_option.should_provision_build_machine();
58
59 write_environment_details(
60 &self.s3_repository,
61 &options.name,
62 &EnvironmentDetails {
63 deployment_type: DeploymentType::Bootstrap,
64 environment_type: options.environment_type.clone(),
65 evm_details: EvmDetails {
66 network: options.evm_network.clone(),
67 data_payments_address: options.evm_data_payments_address.clone(),
68 payment_token_address: options.evm_payment_token_address.clone(),
69 rpc_url: options.evm_rpc_url.clone(),
70 },
71 funding_wallet_address: None,
72 network_id: Some(options.network_id),
73 region: options.region.clone(),
74 rewards_address: Some(options.rewards_address.clone()),
75 },
76 )
77 .await?;
78
79 self.create_or_update_infra(&InfraRunOptions {
80 client_image_id: None,
81 client_vm_count: Some(0),
82 client_vm_size: None,
83 enable_build_vm: build_custom_binaries,
84 evm_node_count: Some(0),
85 evm_node_vm_size: None,
86 evm_node_image_id: None,
87 full_cone_vm_size: None, full_cone_private_node_vm_count: options.full_cone_private_node_vm_count,
89 full_cone_private_node_volume_size: options.full_cone_private_node_volume_size,
90 genesis_vm_count: Some(0),
91 genesis_node_volume_size: None,
92 name: options.name.clone(),
93 nat_gateway_image_id: None,
94 node_image_id: None,
95 node_vm_count: options.node_vm_count,
96 node_vm_size: options.node_vm_size.clone(),
97 node_volume_size: options.node_volume_size,
98 peer_cache_image_id: None,
99 peer_cache_node_vm_count: Some(0),
100 peer_cache_node_vm_size: None,
101 peer_cache_node_volume_size: None,
102 port_restricted_cone_vm_size: None,
103 port_restricted_private_node_vm_count: Some(0),
104 port_restricted_private_node_volume_size: None,
105 region: options.region.clone(),
106 symmetric_nat_gateway_vm_size: None, symmetric_private_node_vm_count: options.symmetric_private_node_vm_count,
108 symmetric_private_node_volume_size: options.symmetric_private_node_volume_size,
109 tfvars_filenames: Some(
110 options
111 .environment_type
112 .get_tfvars_filenames(&options.name, &options.region),
113 ),
114 upnp_vm_size: None,
115 upnp_private_node_vm_count: options.upnp_private_node_vm_count,
116 upnp_private_node_volume_size: options.upnp_private_node_volume_size,
117 })
118 .map_err(|err| {
119 println!("Failed to create infra {err:?}");
120 err
121 })?;
122
123 let mut provision_options = ProvisionOptions::from(options.clone());
124 if build_custom_binaries {
125 self.ansible_provisioner
126 .print_ansible_run_banner("Build Custom Binaries");
127 self.ansible_provisioner
128 .build_autonomi_binaries(&provision_options, None)
129 .map_err(|err| {
130 println!("Failed to build safe network binaries {err:?}");
131 err
132 })?;
133 }
134
135 let mut failed_to_provision = false;
136
137 self.ansible_provisioner
138 .print_ansible_run_banner("Provision Public Nodes");
139 match self.ansible_provisioner.provision_nodes(
140 &provision_options,
141 options.peer.clone(),
142 options.network_contacts_url.clone(),
143 NodeType::Generic,
144 ) {
145 Ok(()) => {
146 println!("Provisioned public nodes");
147 }
148 Err(e) => {
149 println!("Failed to provision public nodes: {e:?}");
150 failed_to_provision = true;
151 }
152 }
153
154 self.ansible_provisioner
155 .print_ansible_run_banner("Provision UPnP Nodes");
156 match self.ansible_provisioner.provision_nodes(
157 &provision_options,
158 options.peer.clone(),
159 options.network_contacts_url.clone(),
160 NodeType::Upnp,
161 ) {
162 Ok(()) => {
163 println!("Provisioned UPnP nodes");
164 }
165 Err(e) => {
166 error!("Failed to provision UPnP nodes: {e:?}");
167 failed_to_provision = true;
168 }
169 }
170
171 let private_node_inventory = PrivateNodeProvisionInventory::new(
172 &self.ansible_provisioner,
173 options.full_cone_private_node_vm_count,
174 options.symmetric_private_node_vm_count,
175 None, )?;
177
178 if private_node_inventory.should_provision_full_cone_private_nodes() {
179 match self.ansible_provisioner.provision_full_cone(
180 &provision_options,
181 options.peer.clone(),
182 options.network_contacts_url.clone(),
183 private_node_inventory.clone(),
184 None,
185 ) {
186 Ok(()) => {
187 println!("Provisioned full cone nodes and gateway");
188 }
189 Err(err) => {
190 error!("Failed to provision full cone nodes and gateway: {err}");
191 failed_to_provision = true;
192 }
193 }
194 }
195
196 if private_node_inventory.should_provision_symmetric_private_nodes() {
197 self.ansible_provisioner
198 .print_ansible_run_banner("Provision Symmetric NAT Gateway");
199 self.ansible_provisioner
200 .provision_symmetric_nat_gateway(&provision_options, &private_node_inventory)
201 .map_err(|err| {
202 println!("Failed to provision symmetric NAT gateway {err:?}");
203 err
204 })?;
205
206 self.ansible_provisioner
207 .print_ansible_run_banner("Provision Symmetric Private Nodes");
208 match self.ansible_provisioner.provision_symmetric_private_nodes(
209 &mut provision_options,
210 options.peer.clone(),
211 options.network_contacts_url.clone(),
212 &private_node_inventory,
213 ) {
214 Ok(()) => {
215 println!("Provisioned symmetric private nodes");
216 }
217 Err(err) => {
218 error!("Failed to provision symmetric private nodes: {err}");
219 failed_to_provision = true;
220 }
221 }
222 }
223
224 if failed_to_provision {
225 println!("{}", "WARNING!".yellow());
226 println!("Some nodes failed to provision without error.");
227 println!("This usually means a small number of nodes failed to start on a few VMs.");
228 println!("However, most of the time the deployment will still be usable.");
229 println!("See the output from Ansible to determine which VMs had failures.");
230 }
231
232 Ok(())
233 }
234}