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