Skip to content

Commit bbfddd8

Browse files
committed
feat: Add functionality to download Ubuntu cloud image and initialize VM
- Updated Cargo.toml to include reqwest for HTTP requests and added net feature to tokio. - Implemented download_ubuntu_cloud_image function in src/helpers/init.rs to download the Ubuntu cloud image if it doesn't already exist. - Created a new init module in src/helpers/mod.rs to encapsulate the initialization logic. - Modified main.rs to add an Init command that triggers the image download. - Introduced qmp module in src/vm/mod.rs for QEMU Machine Protocol interactions. - Added functions in src/vm/qmp.rs to start a VM and connect to its QMP socket. - Updated create_seed_iso function in src/vm/utils.rs to change the seed file extension from .iso to .img.
1 parent dcab7f7 commit bbfddd8

8 files changed

Lines changed: 1392 additions & 17 deletions

File tree

Cargo.lock

Lines changed: 1264 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ virt-sys = "0.3.1"
1111
# For file system operations and error handling
1212
anyhow = "1.0"
1313
# For running asynchronous operations
14-
tokio = { version = "1.0", features = ["full"] }
14+
reqwest = { version = "0.13", features = ["json"] }
15+
tokio = { version = "1.0", features = ["full", "net"] }
1516
clap = { version = "4.0", features = ["derive"] }
1617
serde = { version = "1.0", features = ["derive"] }
1718
serde_json = "1.0"

src/helpers/init.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use reqwest::get;
2+
use std::fs::{self, File};
3+
use std::path::Path;
4+
5+
async fn download_ubuntu_cloud_image() -> Result<String, Box<dyn std::error::Error>> {
6+
let url = "https://cloud-images.ubuntu.com/releases/noble/release/ubuntu-24.04-server-cloudimg-amd64.img";
7+
8+
let dir = Path::new("./vm-data");
9+
let file_path = dir.join("ubuntu-24.04-server-cloudimg-amd64.img");
10+
11+
// 1. Create directory if it doesn't exist
12+
if !dir.exists() {
13+
fs::create_dir_all(dir)?;
14+
println!("Created directory: {:?}", dir);
15+
}
16+
17+
// 2. Skip if file already exists
18+
if file_path.exists() {
19+
println!("Image already exists at {:?}", file_path);
20+
return Ok(file_path.to_string_lossy().to_string());
21+
}
22+
23+
println!("Downloading Ubuntu cloud image...");
24+
25+
// 3. Download the file
26+
let mut response = get(url).await?;
27+
28+
if !response.status().is_success() {
29+
return Err(format!("Download failed: {}", response.status()).into());
30+
}
31+
32+
// 4. Create file
33+
let mut file = File::create(&file_path)?;
34+
35+
// 5. Write response to file
36+
while let Some(chunk) = response.chunk().await? {
37+
std::io::copy(&mut chunk.as_ref(), &mut file)?;
38+
}
39+
40+
println!("Downloaded to {:?}", file_path);
41+
42+
Ok(file_path.to_string_lossy().to_string())
43+
}
44+
45+
pub fn initialize() {
46+
// download image
47+
let rt = tokio::runtime::Runtime::new().unwrap();
48+
match rt.block_on(download_ubuntu_cloud_image()) {
49+
Ok(path) => println!("Image ready at: {}", path),
50+
Err(e) => eprintln!("Failed to download image: {}", e),
51+
}
52+
}

src/helpers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use serde::{Serialize, de::DeserializeOwned};
22
use serde_json::Value;
33
use xmltree::{Element, XMLNode};
4+
pub mod init;
45

56
/// Recursively convert JSON value into XML element
67
fn value_to_xml(value: &Value, tag: &str) -> Element {

src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ struct Cli {
1212

1313
#[derive(Subcommand)]
1414
enum Commands {
15+
Init,
1516
Create {
1617
/// Name of the VM
1718
#[arg(short, long)]
@@ -69,6 +70,9 @@ fn main() {
6970
let cli = Cli::parse();
7071

7172
match cli.command {
73+
Commands::Init => {
74+
helpers::init::initialize();
75+
}
7276
Commands::Boot { name } => {
7377
boot_vm(&name);
7478
}

src/vm/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use virt::domain::Domain;
33

44
//use crate::helpers;
55

6+
pub mod qmp;
67
pub mod types;
78
pub mod utils;
89

src/vm/qmp.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use std::io::{Read, Write};
2+
use std::os::unix::net::UnixStream;
3+
use std::process::{Command, Stdio};
4+
5+
pub fn start_vm(disk: &str, seed: &str, qmp_socket: &str) {
6+
let child = Command::new("qemu-system-x86_64")
7+
.args([
8+
"-m",
9+
"2048",
10+
"-smp",
11+
"2",
12+
"-enable-kvm",
13+
"-cpu",
14+
"host",
15+
"-nographic",
16+
// OS disk
17+
"-drive",
18+
&format!("file={},format=qcow2,if=virtio", disk),
19+
// cloud-init seed
20+
"-drive",
21+
&format!("file={},format=raw,media=cdrom", seed),
22+
// networking
23+
"-netdev",
24+
"user,id=net0,hostfwd=tcp::2222-:22",
25+
"-device",
26+
"virtio-net-pci,netdev=net0",
27+
// QMP socket
28+
"-qmp",
29+
&format!("unix:{},server,nowait", qmp_socket),
30+
])
31+
.stdout(Stdio::null())
32+
.stderr(Stdio::null())
33+
.spawn()
34+
.expect("Failed to start QEMU");
35+
36+
println!("Started VM with PID: {}", child.id());
37+
}
38+
39+
pub fn connect_qmp(path: &str) -> UnixStream {
40+
let mut stream: UnixStream;
41+
42+
loop {
43+
match UnixStream::connect(path) {
44+
Ok(s) => {
45+
stream = s;
46+
break;
47+
}
48+
Err(_) => {
49+
std::thread::sleep(std::time::Duration::from_millis(200));
50+
}
51+
}
52+
}
53+
54+
println!("Connected to QMP");
55+
stream
56+
}
57+
58+
pub fn init_qmp(stream: &mut UnixStream) {
59+
let mut buffer = [0; 1024];
60+
61+
// Read greeting
62+
let _ = stream.read(&mut buffer);
63+
64+
// Send capabilities command
65+
let cmd = r#"{ "execute": "qmp_capabilities" }"#;
66+
stream.write_all(cmd.as_bytes()).unwrap();
67+
}

src/vm/utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ pub fn create_seed_iso(name: &str, username: &str, password: &str) -> String {
238238
println!("User Data YAML:\n{}", user_data_yaml);
239239
println!("Meta Data YAML:\n{}", meta_data_yaml);
240240

241-
let iso_path = format!("/var/lib/libvirt/images/{}-seed.iso", name);
241+
let iso_path = format!("/var/lib/libvirt/images/{}-seed.img", name);
242242
let iso_path_obj = Path::new(&iso_path);
243243
if let Some(parent) = iso_path_obj.parent() {
244244
if !parent.exists() {

0 commit comments

Comments
 (0)