fix(k8s_anywhere): Ensure k3d cluster is started before use
- Refactor k3d cluster management to explicitly start the cluster. - Introduce `start_cluster` function to ensure cluster is running before operations. - Improve error handling and logging during cluster startup. - Update `create_cluster` and other related functions to utilize the new startup mechanism. - Enhance reliability and prevent potential issues caused by an uninitialized cluster. - Add `run_k3d_command` to handle k3d commands with logging and error handling.
This commit is contained in:
parent
23971ecd7c
commit
22752960f9
@ -8,6 +8,7 @@ use harmony::{
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
// let _ = env_logger::Builder::from_default_env().filter_level(log::LevelFilter::Info).try_init();
|
||||||
let lamp_stack = LAMPScore {
|
let lamp_stack = LAMPScore {
|
||||||
name: "harmony-lamp-demo".to_string(),
|
name: "harmony-lamp-demo".to_string(),
|
||||||
domain: Url::Url(url::Url::parse("https://lampdemo.harmony.nationtech.io").unwrap()),
|
domain: Url::Url(url::Url::parse("https://lampdemo.harmony.nationtech.io").unwrap()),
|
||||||
|
@ -116,17 +116,22 @@ impl K8sAnywhereTopology {
|
|||||||
info!("Starting K8sAnywhere installation");
|
info!("Starting K8sAnywhere installation");
|
||||||
self.try_install_k3d().await?;
|
self.try_install_k3d().await?;
|
||||||
let k3d_score = self.get_k3d_installation_score();
|
let k3d_score = self.get_k3d_installation_score();
|
||||||
match k3d_rs::K3d::new(k3d_score.installation_path, Some(k3d_score.cluster_name))
|
// I feel like having to rely on the k3d_rs crate here is a smell
|
||||||
.get_client()
|
// I think we should have a way to interact more deeply with scores/interpret. Maybe the
|
||||||
.await
|
// K3DInstallationScore should expose a method to get_client ? Not too sure what would be a
|
||||||
{
|
// good implementation due to the stateful nature of the k3d thing. Which is why I went
|
||||||
Ok(client) => Ok(Some(K8sState {
|
// with this solution for now
|
||||||
|
let k3d = k3d_rs::K3d::new(k3d_score.installation_path, Some(k3d_score.cluster_name));
|
||||||
|
let state = match k3d.get_client().await {
|
||||||
|
Ok(client) => K8sState {
|
||||||
_client: K8sClient::new(client),
|
_client: K8sClient::new(client),
|
||||||
_source: K8sSource::LocalK3d,
|
_source: K8sSource::LocalK3d,
|
||||||
message: "Successfully installed K3D cluster and acquired client".to_string(),
|
message: "Successfully installed K3D cluster and acquired client".to_string(),
|
||||||
})),
|
},
|
||||||
Err(_) => todo!(),
|
Err(_) => todo!(),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
Ok(Some(state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +159,7 @@ struct K8sAnywhereConfig {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Topology for K8sAnywhereTopology {
|
impl Topology for K8sAnywhereTopology {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
todo!()
|
"K8sAnywhereTopology"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ensure_ready(&self) -> Result<Outcome, InterpretError> {
|
async fn ensure_ready(&self) -> Result<Outcome, InterpretError> {
|
||||||
|
@ -117,7 +117,7 @@ impl K3d {
|
|||||||
/// 2. It has proper executable permissions (on Unix systems)
|
/// 2. It has proper executable permissions (on Unix systems)
|
||||||
/// 3. It responds correctly to a simple command (`k3d --version`)
|
/// 3. It responds correctly to a simple command (`k3d --version`)
|
||||||
pub fn is_installed(&self) -> bool {
|
pub fn is_installed(&self) -> bool {
|
||||||
let binary_path = self.base_dir.join(K3D_BIN_FILE_NAME);
|
let binary_path = self.get_k3d_binary_path();
|
||||||
|
|
||||||
if !binary_path.exists() {
|
if !binary_path.exists() {
|
||||||
debug!("K3d binary not found at {:?}", binary_path);
|
debug!("K3d binary not found at {:?}", binary_path);
|
||||||
@ -131,15 +131,15 @@ impl K3d {
|
|||||||
self.can_execute_binary_check(&binary_path)
|
self.can_execute_binary_check(&binary_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies if the specified cluster is already running
|
/// Verifies if the specified cluster is already created
|
||||||
///
|
///
|
||||||
/// Executes `k3d cluster list <cluster_name>` and checks for a successful response,
|
/// Executes `k3d cluster list <cluster_name>` and checks for a successful response,
|
||||||
/// indicating that the cluster exists and is registered with k3d.
|
/// indicating that the cluster exists and is registered with k3d.
|
||||||
pub fn is_cluster_initialized(&self) -> bool {
|
pub fn is_cluster_initialized(&self) -> bool {
|
||||||
let cluster_name = match &self.cluster_name {
|
let cluster_name = match self.get_cluster_name() {
|
||||||
Some(name) => name,
|
Ok(name) => name,
|
||||||
None => {
|
Err(_) => {
|
||||||
debug!("No cluster name specified, can't verify if cluster is initialized");
|
debug!("Could not get cluster name, can't verify if cluster is initialized");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -152,6 +152,13 @@ impl K3d {
|
|||||||
self.verify_cluster_exists(&binary_path, cluster_name)
|
self.verify_cluster_exists(&binary_path, cluster_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_cluster_name(&self) -> Result<&String, String> {
|
||||||
|
match &self.cluster_name {
|
||||||
|
Some(name) => Ok(name),
|
||||||
|
None => Err("No cluster name available".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new k3d cluster with the specified name
|
/// Creates a new k3d cluster with the specified name
|
||||||
///
|
///
|
||||||
/// This method:
|
/// This method:
|
||||||
@ -163,22 +170,29 @@ impl K3d {
|
|||||||
/// - `Ok(Client)` - Successfully created cluster and connected client
|
/// - `Ok(Client)` - Successfully created cluster and connected client
|
||||||
/// - `Err(String)` - Error message detailing what went wrong
|
/// - `Err(String)` - Error message detailing what went wrong
|
||||||
pub async fn initialize_cluster(&self) -> Result<Client, String> {
|
pub async fn initialize_cluster(&self) -> Result<Client, String> {
|
||||||
let cluster_name = match &self.cluster_name {
|
let cluster_name = match self.get_cluster_name() {
|
||||||
Some(name) => name,
|
Ok(name) => name,
|
||||||
None => return Err("No cluster name specified for initialization".to_string()),
|
Err(_) => return Err("Could not get cluster_name, cannot initialize".to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let binary_path = self.base_dir.join(K3D_BIN_FILE_NAME);
|
|
||||||
if !binary_path.exists() {
|
|
||||||
return Err(format!("K3d binary not found at {:?}", binary_path));
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Initializing k3d cluster '{}'", cluster_name);
|
info!("Initializing k3d cluster '{}'", cluster_name);
|
||||||
|
|
||||||
self.create_cluster(&binary_path, cluster_name)?;
|
self.create_cluster(cluster_name)?;
|
||||||
self.create_kubernetes_client().await
|
self.create_kubernetes_client().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_k3d_binary_path(&self) -> PathBuf {
|
||||||
|
self.base_dir.join(K3D_BIN_FILE_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_k3d_binary(&self) -> Result<PathBuf, String> {
|
||||||
|
let path = self.get_k3d_binary_path();
|
||||||
|
if !path.exists() {
|
||||||
|
return Err(format!("K3d binary not found at {:?}", path));
|
||||||
|
}
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
/// Ensures k3d is installed and the cluster is initialized
|
/// Ensures k3d is installed and the cluster is initialized
|
||||||
///
|
///
|
||||||
/// This method provides a complete setup flow:
|
/// This method provides a complete setup flow:
|
||||||
@ -206,6 +220,8 @@ impl K3d {
|
|||||||
return self.initialize_cluster().await;
|
return self.initialize_cluster().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.start_cluster().await?;
|
||||||
|
|
||||||
info!("K3d and cluster are already properly set up");
|
info!("K3d and cluster are already properly set up");
|
||||||
self.create_kubernetes_client().await
|
self.create_kubernetes_client().await
|
||||||
}
|
}
|
||||||
@ -282,11 +298,27 @@ impl K3d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_cluster(&self, binary_path: &PathBuf, cluster_name: &str) -> Result<(), String> {
|
pub fn run_k3d_command<I, S>(&self, args: I) -> Result<std::process::Output, String>
|
||||||
let output = std::process::Command::new(binary_path)
|
where
|
||||||
.args(["cluster", "create", cluster_name])
|
I: IntoIterator<Item = S>,
|
||||||
.output()
|
S: AsRef<std::ffi::OsStr>,
|
||||||
.map_err(|e| format!("Failed to execute k3d command: {}", e))?;
|
{
|
||||||
|
let binary_path = self.get_k3d_binary()?;
|
||||||
|
let output = std::process::Command::new(binary_path).args(args).output();
|
||||||
|
match output {
|
||||||
|
Ok(output) => {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
debug!("stderr : {}", stderr);
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
debug!("stdout : {}", stdout);
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
Err(e) => Err(format!("Failed to execute k3d command: {}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_cluster(&self, cluster_name: &str) -> Result<(), String> {
|
||||||
|
let output = self.run_k3d_command(["cluster", "create", cluster_name])?;
|
||||||
|
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
@ -310,6 +342,19 @@ impl K3d {
|
|||||||
false => Err("Cannot get client! Cluster not initialized yet".to_string()),
|
false => Err("Cannot get client! Cluster not initialized yet".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn start_cluster(&self) -> Result<(), String> {
|
||||||
|
let cluster_name = self.get_cluster_name()?;
|
||||||
|
let output = self.run_k3d_command(["cluster", "start", cluster_name])?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(format!("Failed to start cluster: {}", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Successfully started k3d cluster '{}'", cluster_name);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -23,7 +23,7 @@ pub struct Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Config {
|
impl Serialize for Config {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
|
@ -10,11 +10,11 @@ mod test {
|
|||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
|
|
||||||
#[cfg(opnsenseendtoend)]
|
#[cfg(opnsenseendtoend)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_public_sdk() {
|
async fn test_public_sdk() {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
let mac = "11:22:33:44:55:66";
|
let mac = "11:22:33:44:55:66";
|
||||||
let ip = Ipv4Addr::new(10, 100, 8, 200);
|
let ip = Ipv4Addr::new(10, 100, 8, 200);
|
||||||
let hostname = "test_hostname";
|
let hostname = "test_hostname";
|
||||||
|
Loading…
Reference in New Issue
Block a user