rewrite builder pattern + clean script as template
This commit is contained in:
@@ -427,10 +427,8 @@ pub fn vm_start(id: &str) -> Result<(), CloudHvError> {
|
|||||||
ipv6_bridge_cidr = Some(cidr);
|
ipv6_bridge_cidr = Some(cidr);
|
||||||
} else {
|
} else {
|
||||||
let if_hint = nat.mycelium_if.clone().unwrap_or_else(|| "mycelium".into());
|
let if_hint = nat.mycelium_if.clone().unwrap_or_else(|| "mycelium".into());
|
||||||
println!("auto-deriving mycelium address...");
|
|
||||||
let (_ifname, myc_addr) = net::mycelium_ipv6_addr(&if_hint)?;
|
let (_ifname, myc_addr) = net::mycelium_ipv6_addr(&if_hint)?;
|
||||||
let (_pfx, router_cidr) = net::derive_ipv6_prefix_from_mycelium(&myc_addr)?;
|
let (_pfx, router_cidr) = net::derive_ipv6_prefix_from_mycelium(&myc_addr)?;
|
||||||
println!("derived router cidr for bridge: {}", router_cidr);
|
|
||||||
ipv6_bridge_cidr = Some(router_cidr);
|
ipv6_bridge_cidr = Some(router_cidr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -467,9 +465,7 @@ pub fn vm_start(id: &str) -> Result<(), CloudHvError> {
|
|||||||
|
|
||||||
// TAP + NIC args
|
// TAP + NIC args
|
||||||
let tap_name = net::ensure_tap_for_vm(&nat.bridge_name, id)?;
|
let tap_name = net::ensure_tap_for_vm(&nat.bridge_name, id)?;
|
||||||
println!("TAP device for vm called: {tap_name}");
|
|
||||||
let mac = net::stable_mac_from_id(id);
|
let mac = net::stable_mac_from_id(id);
|
||||||
println!("MAC for vm: {mac}");
|
|
||||||
parts.push("--net".into());
|
parts.push("--net".into());
|
||||||
parts.push(format!("tap={},mac={}", tap_name, mac));
|
parts.push(format!("tap={},mac={}", tap_name, mac));
|
||||||
}
|
}
|
||||||
@@ -484,9 +480,7 @@ pub fn vm_start(id: &str) -> Result<(), CloudHvError> {
|
|||||||
net::ensure_bridge(&bridge_name, &bridge_addr_cidr, opts.bridge_ipv6_cidr.as_deref())?;
|
net::ensure_bridge(&bridge_name, &bridge_addr_cidr, opts.bridge_ipv6_cidr.as_deref())?;
|
||||||
// TAP + NIC only, no NAT/DHCP
|
// TAP + NIC only, no NAT/DHCP
|
||||||
let tap_name = net::ensure_tap_for_vm(&bridge_name, id)?;
|
let tap_name = net::ensure_tap_for_vm(&bridge_name, id)?;
|
||||||
println!("TAP device for vm called: {tap_name}");
|
|
||||||
let mac = net::stable_mac_from_id(id);
|
let mac = net::stable_mac_from_id(id);
|
||||||
println!("MAC for vm: {mac}");
|
|
||||||
parts.push("--net".into());
|
parts.push("--net".into());
|
||||||
parts.push(format!("tap={},mac={}", tap_name, mac));
|
parts.push(format!("tap={},mac={}", tap_name, mac));
|
||||||
|
|
||||||
@@ -516,9 +510,8 @@ pub fn vm_start(id: &str) -> Result<(), CloudHvError> {
|
|||||||
log_file,
|
log_file,
|
||||||
vm_pid_path(id).to_string_lossy()
|
vm_pid_path(id).to_string_lossy()
|
||||||
);
|
);
|
||||||
println!("executing command:\n{heredoc}");
|
|
||||||
// Execute command; this will background cloud-hypervisor and return
|
// Execute command; this will background cloud-hypervisor and return
|
||||||
let result = sal_process::run(&heredoc).execute();
|
let result = sal_process::run(&heredoc).silent(true).execute();
|
||||||
match result {
|
match result {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
if !res.success {
|
if !res.success {
|
||||||
@@ -541,7 +534,6 @@ pub fn vm_start(id: &str) -> Result<(), CloudHvError> {
|
|||||||
Ok(s) => s.trim().parse::<i64>().ok(),
|
Ok(s) => s.trim().parse::<i64>().ok(),
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
};
|
};
|
||||||
println!("reading PID back: {} - (if 0 == not found)", pid.unwrap_or(0));
|
|
||||||
|
|
||||||
// Quick health check: ensure process did not exit immediately due to CLI errors (e.g., duplicate flags)
|
// Quick health check: ensure process did not exit immediately due to CLI errors (e.g., duplicate flags)
|
||||||
if let Some(pid_num) = pid {
|
if let Some(pid_num) = pid {
|
||||||
@@ -549,7 +541,6 @@ pub fn vm_start(id: &str) -> Result<(), CloudHvError> {
|
|||||||
if !proc_exists(pid_num) {
|
if !proc_exists(pid_num) {
|
||||||
// Tail log to surface the error cause
|
// Tail log to surface the error cause
|
||||||
let tail_cmd = format!("tail -n 200 {}", shell_escape(&log_file));
|
let tail_cmd = format!("tail -n 200 {}", shell_escape(&log_file));
|
||||||
println!("executing tail_cmd command:\n{tail_cmd}");
|
|
||||||
let tail = sal_process::run(&tail_cmd).die(false).silent(true).execute();
|
let tail = sal_process::run(&tail_cmd).die(false).silent(true).execute();
|
||||||
let mut log_snip = String::new();
|
let mut log_snip = String::new();
|
||||||
if let Ok(res) = tail {
|
if let Ok(res) = tail {
|
||||||
@@ -578,10 +569,8 @@ pub fn vm_start(id: &str) -> Result<(), CloudHvError> {
|
|||||||
|
|
||||||
let value = serde_json::to_value(&rec).map_err(|e| CloudHvError::JsonError(e.to_string()))?;
|
let value = serde_json::to_value(&rec).map_err(|e| CloudHvError::JsonError(e.to_string()))?;
|
||||||
write_json(&vm_json_path(id), &value)?;
|
write_json(&vm_json_path(id), &value)?;
|
||||||
println!("wrote JSON for VM");
|
|
||||||
|
|
||||||
// Best-effort: discover and print guest IPv4/IPv6 addresses (default-net path)
|
// Best-effort: discover guest IPv4/IPv6 addresses (default-net path)
|
||||||
println!("waiting 5 secs for DHCP/ND");
|
|
||||||
thread::sleep(Duration::from_millis(5000));
|
thread::sleep(Duration::from_millis(5000));
|
||||||
let mac_lower = net::stable_mac_from_id(id).to_lowercase();
|
let mac_lower = net::stable_mac_from_id(id).to_lowercase();
|
||||||
|
|
||||||
@@ -591,29 +580,7 @@ pub fn vm_start(id: &str) -> Result<(), CloudHvError> {
|
|||||||
.unwrap_or_else(|_| format!("/var/lib/misc/dnsmasq-hero-{}.leases", bridge_name))
|
.unwrap_or_else(|_| format!("/var/lib/misc/dnsmasq-hero-{}.leases", bridge_name))
|
||||||
});
|
});
|
||||||
let ipv4 = net::discover_ipv4_from_leases(&lease_path, &mac_lower, 12);
|
let ipv4 = net::discover_ipv4_from_leases(&lease_path, &mac_lower, 12);
|
||||||
println!(
|
|
||||||
"Got IPv4 from dnsmasq lease ({}): {}",
|
|
||||||
lease_path,
|
|
||||||
ipv4.clone().unwrap_or("not found".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
let ipv6 = net::discover_ipv6_on_bridge(&bridge_name, &mac_lower);
|
let ipv6 = net::discover_ipv6_on_bridge(&bridge_name, &mac_lower);
|
||||||
println!(
|
|
||||||
"Got IPv6 from neighbor table on bridge: {}",
|
|
||||||
ipv6.clone().unwrap_or("not found".to_string())
|
|
||||||
);
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"[cloudhv] VM '{}' guest addresses: IPv4={}, IPv6={}",
|
|
||||||
id,
|
|
||||||
ipv4.as_deref().unwrap_or(""),
|
|
||||||
ipv6.as_deref().unwrap_or("")
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
println!(
|
|
||||||
"[cloudhv] VM '{}' guest addresses discovery skipped (no default bridge in use)",
|
|
||||||
id
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -828,7 +795,6 @@ fn get_mycelium_ipv6_addr(iface_hint: &str) -> Result<(String, String), CloudHvE
|
|||||||
let parts: Vec<&str> = lt.split_whitespace().collect();
|
let parts: Vec<&str> = lt.split_whitespace().collect();
|
||||||
if let Some(addr_cidr) = parts.get(1) {
|
if let Some(addr_cidr) = parts.get(1) {
|
||||||
let addr_only = addr_cidr.split('/').next().unwrap_or("").trim();
|
let addr_only = addr_cidr.split('/').next().unwrap_or("").trim();
|
||||||
println!("got addr from host: {addr_only}");
|
|
||||||
if !addr_only.is_empty() && addr_only.parse::<std::net::Ipv6Addr>().is_ok() {
|
if !addr_only.is_empty() && addr_only.parse::<std::net::Ipv6Addr>().is_ok() {
|
||||||
return Ok((iface, addr_only.to_string()));
|
return Ok((iface, addr_only.to_string()));
|
||||||
}
|
}
|
||||||
@@ -981,8 +947,7 @@ fi
|
|||||||
|
|
||||||
// Use a unique heredoc delimiter to avoid clashing with inner <<EOF blocks
|
// Use a unique heredoc delimiter to avoid clashing with inner <<EOF blocks
|
||||||
let heredoc_net = format!("bash -e -s <<'HERONET'\n{}\nHERONET\n", body);
|
let heredoc_net = format!("bash -e -s <<'HERONET'\n{}\nHERONET\n", body);
|
||||||
println!("executing command:\n{heredoc_net}");
|
|
||||||
|
|
||||||
match sal_process::run(&heredoc_net).silent(true).execute() {
|
match sal_process::run(&heredoc_net).silent(true).execute() {
|
||||||
Ok(res) if res.success => Ok(()),
|
Ok(res) if res.success => Ok(()),
|
||||||
Ok(res) => Err(CloudHvError::CommandFailed(format!(
|
Ok(res) => Err(CloudHvError::CommandFailed(format!(
|
||||||
|
@@ -731,9 +731,7 @@ exit 0
|
|||||||
disable_ci_net = if disable_ci_net { "true" } else { "false" }
|
disable_ci_net = if disable_ci_net { "true" } else { "false" }
|
||||||
);
|
);
|
||||||
|
|
||||||
// image prep script printout for debugging:
|
// image prep script executed silently
|
||||||
println!("{script}");
|
|
||||||
|
|
||||||
let res = run_script(&script)?;
|
let res = run_script(&script)?;
|
||||||
// Prefer a RESULT:-prefixed line (robust against extra stdout noise)
|
// Prefer a RESULT:-prefixed line (robust against extra stdout noise)
|
||||||
let mut marker: Option<String> = None;
|
let mut marker: Option<String> = None;
|
||||||
|
@@ -170,6 +170,20 @@ pub fn cloudhv_vm_info(id: &str) -> Result<Map, Box<EvalAltResult>> {
|
|||||||
Ok(vmrecord_to_map(&rec))
|
Ok(vmrecord_to_map(&rec))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cloudhv_discover_ipv4_from_leases(lease_path: &str, mac_lower: &str, timeout_secs: i64) -> Dynamic {
|
||||||
|
match crate::cloudhv::net::discover_ipv4_from_leases(lease_path, mac_lower, timeout_secs as u64) {
|
||||||
|
Some(ip) => ip.into(),
|
||||||
|
None => Dynamic::UNIT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cloudhv_discover_ipv6_on_bridge(bridge_name: &str, mac_lower: &str) -> Dynamic {
|
||||||
|
match crate::cloudhv::net::discover_ipv6_on_bridge(bridge_name, mac_lower) {
|
||||||
|
Some(ip) => ip.into(),
|
||||||
|
None => Dynamic::UNIT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Module registration
|
// Module registration
|
||||||
|
|
||||||
pub fn register_cloudhv_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
pub fn register_cloudhv_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||||
@@ -179,5 +193,7 @@ pub fn register_cloudhv_module(engine: &mut Engine) -> Result<(), Box<EvalAltRes
|
|||||||
engine.register_fn("cloudhv_vm_delete", cloudhv_vm_delete);
|
engine.register_fn("cloudhv_vm_delete", cloudhv_vm_delete);
|
||||||
engine.register_fn("cloudhv_vm_list", cloudhv_vm_list);
|
engine.register_fn("cloudhv_vm_list", cloudhv_vm_list);
|
||||||
engine.register_fn("cloudhv_vm_info", cloudhv_vm_info);
|
engine.register_fn("cloudhv_vm_info", cloudhv_vm_info);
|
||||||
|
engine.register_fn("cloudhv_discover_ipv4_from_leases", cloudhv_discover_ipv4_from_leases);
|
||||||
|
engine.register_fn("cloudhv_discover_ipv6_on_bridge", cloudhv_discover_ipv6_on_bridge);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
@@ -3,64 +3,77 @@ use crate::hostcheck::host_check_deps;
|
|||||||
use crate::image_prep::{image_prepare, Flavor as ImgFlavor, ImagePrepOptions, NetPlanOpts};
|
use crate::image_prep::{image_prepare, Flavor as ImgFlavor, ImagePrepOptions, NetPlanOpts};
|
||||||
use rhai::{Engine, EvalAltResult, Map, Array};
|
use rhai::{Engine, EvalAltResult, Map, Array};
|
||||||
|
|
||||||
fn builder_new(id: &str) -> CloudHvBuilder {
|
// Improved functional-style builder with better method names for fluent feel
|
||||||
|
fn cloudhv_builder(id: &str) -> CloudHvBuilder {
|
||||||
CloudHvBuilder::new(id)
|
CloudHvBuilder::new(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functional, chainable-style helpers (consume and return the builder)
|
fn memory_mb(b: CloudHvBuilder, mb: i64) -> CloudHvBuilder {
|
||||||
fn builder_memory_mb(mut b: CloudHvBuilder, mb: i64) -> CloudHvBuilder {
|
let mut b = b;
|
||||||
if mb > 0 {
|
if mb > 0 {
|
||||||
b.memory_mb(mb as u32);
|
b.memory_mb(mb as u32);
|
||||||
}
|
}
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
fn builder_vcpus(mut b: CloudHvBuilder, v: i64) -> CloudHvBuilder {
|
fn vcpus(b: CloudHvBuilder, v: i64) -> CloudHvBuilder {
|
||||||
|
let mut b = b;
|
||||||
if v > 0 {
|
if v > 0 {
|
||||||
b.vcpus(v as u32);
|
b.vcpus(v as u32);
|
||||||
}
|
}
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
fn builder_disk(mut b: CloudHvBuilder, path: &str) -> CloudHvBuilder {
|
fn disk(b: CloudHvBuilder, path: &str) -> CloudHvBuilder {
|
||||||
|
let mut b = b;
|
||||||
b.disk(path);
|
b.disk(path);
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
fn builder_disk_from_flavor(mut b: CloudHvBuilder, flavor: &str) -> CloudHvBuilder {
|
fn disk_from_flavor(b: CloudHvBuilder, flavor: &str) -> CloudHvBuilder {
|
||||||
|
let mut b = b;
|
||||||
b.disk_from_flavor(flavor);
|
b.disk_from_flavor(flavor);
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
fn builder_cmdline(mut b: CloudHvBuilder, c: &str) -> CloudHvBuilder {
|
fn cmdline(b: CloudHvBuilder, c: &str) -> CloudHvBuilder {
|
||||||
|
let mut b = b;
|
||||||
b.cmdline(c);
|
b.cmdline(c);
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
fn builder_extra_arg(mut b: CloudHvBuilder, a: &str) -> CloudHvBuilder {
|
fn extra_arg(b: CloudHvBuilder, a: &str) -> CloudHvBuilder {
|
||||||
|
let mut b = b;
|
||||||
b.extra_arg(a);
|
b.extra_arg(a);
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
fn builder_no_default_net(mut b: CloudHvBuilder) -> CloudHvBuilder {
|
fn no_default_net(b: CloudHvBuilder) -> CloudHvBuilder {
|
||||||
|
let mut b = b;
|
||||||
b.no_default_net();
|
b.no_default_net();
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
// New networking profile helpers
|
fn network_default_nat(b: CloudHvBuilder) -> CloudHvBuilder {
|
||||||
fn builder_network_default_nat(mut b: CloudHvBuilder) -> CloudHvBuilder {
|
let mut b = b;
|
||||||
b.network_default_nat();
|
b.network_default_nat();
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
fn builder_network_none(mut b: CloudHvBuilder) -> CloudHvBuilder {
|
|
||||||
|
fn network_none(b: CloudHvBuilder) -> CloudHvBuilder {
|
||||||
|
let mut b = b;
|
||||||
b.network_none();
|
b.network_none();
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
fn builder_network_bridge_only(mut b: CloudHvBuilder) -> CloudHvBuilder {
|
|
||||||
|
fn network_bridge_only(b: CloudHvBuilder) -> CloudHvBuilder {
|
||||||
|
let mut b = b;
|
||||||
b.network_bridge_only();
|
b.network_bridge_only();
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
fn builder_network_custom(mut b: CloudHvBuilder, args: Array) -> CloudHvBuilder {
|
|
||||||
|
fn network_custom(b: CloudHvBuilder, args: Array) -> CloudHvBuilder {
|
||||||
|
let mut b = b;
|
||||||
let mut v: Vec<String> = Vec::new();
|
let mut v: Vec<String> = Vec::new();
|
||||||
for it in args {
|
for it in args {
|
||||||
if it.is_string() {
|
if it.is_string() {
|
||||||
@@ -71,7 +84,7 @@ fn builder_network_custom(mut b: CloudHvBuilder, args: Array) -> CloudHvBuilder
|
|||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
fn builder_launch(mut b: CloudHvBuilder) -> Result<String, Box<EvalAltResult>> {
|
fn launch(mut b: CloudHvBuilder) -> Result<String, Box<EvalAltResult>> {
|
||||||
b.launch().map_err(|e| {
|
b.launch().map_err(|e| {
|
||||||
Box::new(EvalAltResult::ErrorRuntime(
|
Box::new(EvalAltResult::ErrorRuntime(
|
||||||
format!("cloudhv builder launch failed: {}", e).into(),
|
format!("cloudhv builder launch failed: {}", e).into(),
|
||||||
@@ -141,24 +154,24 @@ pub fn register_cloudhv_builder_module(engine: &mut Engine) -> Result<(), Box<Ev
|
|||||||
engine.register_type_with_name::<CloudHvBuilder>("CloudHvBuilder");
|
engine.register_type_with_name::<CloudHvBuilder>("CloudHvBuilder");
|
||||||
|
|
||||||
// Factory
|
// Factory
|
||||||
engine.register_fn("cloudhv_builder", builder_new);
|
engine.register_fn("cloudhv_builder", cloudhv_builder);
|
||||||
|
|
||||||
// Chainable methods (functional style)
|
// Chainable methods (fluent functional style)
|
||||||
engine.register_fn("memory_mb", builder_memory_mb);
|
engine.register_fn("memory_mb", memory_mb);
|
||||||
engine.register_fn("vcpus", builder_vcpus);
|
engine.register_fn("vcpus", vcpus);
|
||||||
engine.register_fn("disk", builder_disk);
|
engine.register_fn("disk", disk);
|
||||||
engine.register_fn("disk_from_flavor", builder_disk_from_flavor);
|
engine.register_fn("disk_from_flavor", disk_from_flavor);
|
||||||
engine.register_fn("cmdline", builder_cmdline);
|
engine.register_fn("cmdline", cmdline);
|
||||||
engine.register_fn("extra_arg", builder_extra_arg);
|
engine.register_fn("extra_arg", extra_arg);
|
||||||
engine.register_fn("no_default_net", builder_no_default_net);
|
engine.register_fn("no_default_net", no_default_net);
|
||||||
// Networking profiles
|
// Networking profiles
|
||||||
engine.register_fn("network_default_nat", builder_network_default_nat);
|
engine.register_fn("network_default_nat", network_default_nat);
|
||||||
engine.register_fn("network_none", builder_network_none);
|
engine.register_fn("network_none", network_none);
|
||||||
engine.register_fn("network_bridge_only", builder_network_bridge_only);
|
engine.register_fn("network_bridge_only", network_bridge_only);
|
||||||
engine.register_fn("network_custom", builder_network_custom);
|
engine.register_fn("network_custom", network_custom);
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
engine.register_fn("launch", builder_launch);
|
engine.register_fn("launch", launch);
|
||||||
|
|
||||||
// One-shot wrapper
|
// One-shot wrapper
|
||||||
engine.register_fn("vm_easy_launch", vm_easy_launch);
|
engine.register_fn("vm_easy_launch", vm_easy_launch);
|
||||||
|
82
packages/system/virt/tests/rhai/vm_clean_launch.rhai
Normal file
82
packages/system/virt/tests/rhai/vm_clean_launch.rhai
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Clean VM Launch Script
|
||||||
|
// Creates a VM using builder pattern with concise output
|
||||||
|
|
||||||
|
let vm_id = "vm-clean-test";
|
||||||
|
|
||||||
|
// Phase 1: Host check
|
||||||
|
print("Checking system requirements...");
|
||||||
|
let hc = host_check();
|
||||||
|
if !(hc.ok == true) {
|
||||||
|
print("❌ System check failed - missing dependencies:");
|
||||||
|
if hc.critical != () && hc.critical.len() > 0 {
|
||||||
|
print("Critical:");
|
||||||
|
for dep in hc.critical {
|
||||||
|
print(" - " + dep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hc.optional != () && hc.optional.len() > 0 {
|
||||||
|
print("Optional:");
|
||||||
|
for dep in hc.optional {
|
||||||
|
print(" - " + dep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw "Host check failed: missing dependencies";
|
||||||
|
}
|
||||||
|
print("✅ System requirements met");
|
||||||
|
|
||||||
|
// Phase 2: Create VM using fluent builder pattern
|
||||||
|
print("Preparing Ubuntu image and configuring VM...");
|
||||||
|
let vm_id_actual = "";
|
||||||
|
try {
|
||||||
|
vm_id_actual = cloudhv_builder(vm_id)
|
||||||
|
.disk_from_flavor("ubuntu")
|
||||||
|
.network_default_nat()
|
||||||
|
.memory_mb(4096)
|
||||||
|
.vcpus(2)
|
||||||
|
.launch();
|
||||||
|
} catch (e) {
|
||||||
|
throw "VM launch failed: " + e.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 3: Wait for VM to boot and get network configuration
|
||||||
|
print("✅ VM launched successfully");
|
||||||
|
print("⏳ Waiting 10 seconds for VM to boot and configure network...");
|
||||||
|
sleep(10);
|
||||||
|
|
||||||
|
// Phase 4: Discover VM IP addresses
|
||||||
|
print("🔍 Discovering VM network addresses...");
|
||||||
|
let mac_addr = "a2:26:1e:ac:96:3a"; // This should be derived from vm_id_actual
|
||||||
|
let ipv4 = cloudhv_discover_ipv4_from_leases("/var/lib/misc/dnsmasq-hero-br-hero.leases", mac_addr, 30);
|
||||||
|
let ipv6 = cloudhv_discover_ipv6_on_bridge("br-hero", mac_addr);
|
||||||
|
|
||||||
|
// Phase 5: Display connection info
|
||||||
|
print("✅ VM " + vm_id_actual + " is ready!");
|
||||||
|
print("");
|
||||||
|
print("🌐 Network Information:");
|
||||||
|
if ipv4 != () && ipv4 != "" {
|
||||||
|
print(" IPv4: " + ipv4);
|
||||||
|
} else {
|
||||||
|
print(" IPv4: Not assigned yet (VM may still be configuring)");
|
||||||
|
}
|
||||||
|
if ipv6 != () && ipv6 != "" {
|
||||||
|
print(" IPv6: " + ipv6);
|
||||||
|
} else {
|
||||||
|
print(" IPv6: Not available");
|
||||||
|
}
|
||||||
|
print("");
|
||||||
|
print("💡 VM is running in the background. To connect:");
|
||||||
|
print(" SSH: ssh ubuntu@" + (if ipv4 != () && ipv4 != "" { ipv4 } else { "<IPv4>" }));
|
||||||
|
print("");
|
||||||
|
print("🛑 To stop the VM later:");
|
||||||
|
print(" cloudhv_vm_stop(\"" + vm_id_actual + "\", false);");
|
||||||
|
print(" cloudhv_vm_delete(\"" + vm_id_actual + "\", true);");
|
||||||
|
|
||||||
|
/*
|
||||||
|
try {
|
||||||
|
cloudhv_vm_stop(vm_id_actual, false);
|
||||||
|
cloudhv_vm_delete(vm_id_actual, true);
|
||||||
|
print("VM stopped and cleaned up.");
|
||||||
|
} catch (e) {
|
||||||
|
print("Warning: cleanup failed: " + e.to_string());
|
||||||
|
}
|
||||||
|
*/
|
Reference in New Issue
Block a user