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