sn_testnet_deploy/
bootstrap.rs

1// Copyright (c) 2023, MaidSafe.
2// All rights reserved.
3//
4// This SAFE Network Software is licensed under the BSD-3-Clause license.
5// Please see the LICENSE file for more details.
6
7use 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, // We can take the value from tfvars for bootstrap deployments.
88            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, // We can take the value from tfvars for bootstrap deployments.
107            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, // TODO: Add port restricted cone support to bootstrap
176        )?;
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}