Skip to content

Commit

Permalink
feat: add ansible run to the deploy command
Browse files Browse the repository at this point in the history
Right now this only provisions the genesis node. We need to implement the retrieval of the genesis
IP and peer ID before we can run against the remaining nodes.

It uses a very similar pattern to Terraform, with an interface, so that we can do behaviour-based
unit testing.
  • Loading branch information
jacderida committed Aug 3, 2023
1 parent 9bd6609 commit 8cede34
Show file tree
Hide file tree
Showing 7 changed files with 396 additions and 71 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ name="testnet-deploy"
[dependencies]
clap = { version = "4.2.1", features = ["derive"] }
color-eyre = "0.6.2"
dirs-next = "2.0.0"
dotenv = "0.15.0"
flate2 = "1.0"
indicatif = "0.17.3"
Expand Down
45 changes: 45 additions & 0 deletions resources/scripts/resource-usage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash

NODE_DATA_DIR_PATH=~/.local/share/safe/node
LOGFILE=$NODE_DATA_DIR_PATH/resource-usage.log

exec > >(tee -a $LOGFILE) 2>&1

while true; do
echo "------------------------------------------------------"
echo "Report for $(date)"
echo "------------------------------------------------------"
echo "Checking $(hostname) on $(hostname -I | awk '{print $1}')"
printf "%-52s %-8s %-10s %-10s %s\n" \
"Node " \
"PID" \
"Memory (MB)" \
"CPU (%)" \
"Record Count"
running_process_count=0
for folder in $NODE_DATA_DIR_PATH/*; do
if [ ! -d "$folder" ]; then continue; fi
peer_id=$(basename "$folder")
pid=$(cat "$folder/safenode.pid")
if [ -z "$pid" ]; then
echo "No PID found for $peer_id"
continue
fi
if [ ! -d "/proc/$pid" ]; then
echo "PID $pid for $peer_id is not currently running"
continue
fi
rss=$(ps -p $pid -o rss=)
cpu=$(top -b -n1 -p $pid | awk 'NR>7 {print $9}')
count=$(find "$folder" -name '*' -not -name '*.pid' -type f | wc -l)
printf "%-52s %-8s %-10s %-10s %s\n" \
"$peer_id" \
"$pid" \
"$(awk "BEGIN {print $rss/1024}")" \
"$cpu" \
"$count"
running_process_count=$((running_process_count + 1))
done
echo "Total node processes: $running_process_count"
sleep 10
done
108 changes: 108 additions & 0 deletions src/ansible.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) 2023, MaidSafe.
// All rights reserved.
//
// This SAFE Network Software is licensed under the BSD-3-Clause license.
// Please see the LICENSE file for more details.
use crate::error::Result;
use crate::run_external_command;
use crate::CloudProvider;
#[cfg(test)]
use mockall::automock;
use std::path::PathBuf;

/// Ansible has multiple 'binaries', e.g., `ansible-playbook`, `ansible-inventory` etc. that are
/// wrappers around the main `ansible` program. It would be a bit cumbersome to create a different
/// runner for all of them, so we can just use this enum to control which program to run.
///
/// Ansible is a Python program, so strictly speaking these are not binaries, but we still use them
/// like a program.
pub enum AnsibleBinary {
AnsiblePlaybook,
AnsibleInventory,
Ansible,
}

impl std::fmt::Display for AnsibleBinary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AnsibleBinary::AnsiblePlaybook => write!(f, "ansible-playbook"),
AnsibleBinary::AnsibleInventory => write!(f, "ansible-inventory"),
AnsibleBinary::Ansible => write!(f, "ansible"),
}
}
}

/// Provides an interface for running Ansible.
///
/// This trait exists for unit testing: it enables testing behaviour without actually calling the
/// Ansible process.
#[cfg_attr(test, automock)]
pub trait AnsibleRunnerInterface {
fn run_playbook(
&self,
playbook_path: PathBuf,
inventory_path: PathBuf,
user: String,
extra_vars_document: Option<String>,
) -> Result<()>;
}

pub struct AnsibleRunner {
pub provider: CloudProvider,
pub working_directory_path: PathBuf,
pub ssh_sk_path: PathBuf,
pub vault_password_file_path: PathBuf,
}

impl AnsibleRunner {
pub fn new(
working_directory_path: PathBuf,
provider: CloudProvider,
ssh_sk_path: PathBuf,
vault_password_file_path: PathBuf,
) -> AnsibleRunner {
AnsibleRunner {
provider,
working_directory_path,
ssh_sk_path,
vault_password_file_path,
}
}
}

impl AnsibleRunnerInterface for AnsibleRunner {
fn run_playbook(
&self,
playbook_path: PathBuf,
inventory_path: PathBuf,
user: String,
extra_vars_document: Option<String>,
) -> Result<()> {
// Using `to_string_lossy` will suffice here. With `to_str` returning an `Option`, to avoid
// unwrapping you would need to `ok_or_else` on every path, and maybe even introduce a new
// error variant, which is very cumbersome. These paths are extremely unlikely to have any
// unicode characters in them.
let mut args = vec![
"--inventory".to_string(),
inventory_path.to_string_lossy().to_string(),
"--private-key".to_string(),
self.ssh_sk_path.to_string_lossy().to_string(),
"--user".to_string(),
user,
"--vault-password-file".to_string(),
self.vault_password_file_path.to_string_lossy().to_string(),
];
if let Some(extra_vars) = extra_vars_document {
args.push("--extra-vars".to_string());
args.push(extra_vars);
}
args.push(playbook_path.to_string_lossy().to_string());
run_external_command(
PathBuf::from(AnsibleBinary::AnsiblePlaybook.to_string()),
self.working_directory_path.clone(),
args,
false,
)?;
Ok(())
}
}
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ pub enum Error {
Reqwest(#[from] reqwest::Error),
#[error(transparent)]
TemplateError(#[from] indicatif::style::TemplateError),
#[error("Terraform run failed")]
TerraformRunFailed,
#[error("Command executed with {0} failed. See output for details.")]
ExternalCommandRunFailed(String),
#[error(transparent)]
VarError(#[from] std::env::VarError),
}

0 comments on commit 8cede34

Please sign in to comment.