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