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/// Options for bootstrapping a network from an existing deployment.
19///
20/// For production (network_id=1), both `peer` and `network_contacts_url` can be None.
21///
22/// For any other networks, at least one of these must be provided.
23#[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, // We can take the value from tfvars for bootstrap deployments.
96            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, // We can take the value from tfvars for bootstrap deployments.
115            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, // TODO: Add port restricted cone support to bootstrap
184        )?;
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}