From 986cb5f1ac7e27f9abe61e9d5cd93037644c6d66 Mon Sep 17 00:00:00 2001 From: minco Date: Fri, 15 Aug 2025 13:30:14 +0900 Subject: [PATCH] feat: agent base implementation --- .github/workflows/test.yaml | 44 +++++++ .gitignore | 51 +++++++++ README.md | 1 + agent/Cargo.toml | 3 + agent/crates/vm/Cargo.toml | 7 ++ agent/crates/vm/src/error.rs | 9 ++ agent/crates/vm/src/lib.rs | 4 + agent/crates/vm/src/model.rs | 3 + agent/crates/vm/src/model/disk.rs | 7 ++ agent/crates/vm/src/model/net.rs | 2 + agent/crates/vm/src/model/vm.rs | 15 +++ agent/crates/vm/src/qemu.rs | 4 + agent/crates/vm/src/qemu/config.rs | 7 ++ agent/crates/vm/src/qemu/error.rs | 7 ++ agent/crates/vm/src/qemu/param.rs | 158 ++++++++++++++++++++++++++ agent/crates/vm/src/qemu/runner.rs | 15 +++ agent/crates/vm/src/service.rs | 1 + agent/crates/vm/src/service/runner.rs | 2 + 18 files changed, 340 insertions(+) create mode 100644 .github/workflows/test.yaml create mode 100755 .gitignore create mode 100644 README.md create mode 100644 agent/Cargo.toml create mode 100644 agent/crates/vm/Cargo.toml create mode 100644 agent/crates/vm/src/error.rs create mode 100644 agent/crates/vm/src/lib.rs create mode 100644 agent/crates/vm/src/model.rs create mode 100644 agent/crates/vm/src/model/disk.rs create mode 100644 agent/crates/vm/src/model/net.rs create mode 100644 agent/crates/vm/src/model/vm.rs create mode 100644 agent/crates/vm/src/qemu.rs create mode 100644 agent/crates/vm/src/qemu/config.rs create mode 100644 agent/crates/vm/src/qemu/error.rs create mode 100644 agent/crates/vm/src/qemu/param.rs create mode 100644 agent/crates/vm/src/qemu/runner.rs create mode 100644 agent/crates/vm/src/service.rs create mode 100644 agent/crates/vm/src/service/runner.rs diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..ba8abed --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,44 @@ +name: Rust CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions/setup-rust@v2 + with: + rust-version: stable + + - name: Cache Cargo registry + uses: actions/cache@v3 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-registry- + + - name: Cache Cargo build + uses: actions/cache@v3 + with: + path: target + key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-build- + + - name: Build + run: cargo build --verbose + + - name: Run tests + run: cargo test --verbose diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..21dd305 --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer,react +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,rust-analyzer,react + +### react ### +.DS_* +*.log +logs +**/*.backup.* +**/*.back.* + +.env +**/.env +.env.* +**/.env.* + +node_modules +bower_components + +*.sublime* + +psd +thumb +sketch + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### rust-analyzer ### +# Can be generated by other build systems other than cargo (ex: bazelbuild/rust_rules) +rust-project.json + + +# End of https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer,react + +.venv + +build +.VSCodeCounter diff --git a/README.md b/README.md new file mode 100644 index 0000000..c02ad41 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Democracy Linux diff --git a/agent/Cargo.toml b/agent/Cargo.toml new file mode 100644 index 0000000..a488447 --- /dev/null +++ b/agent/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "3" +members = ["crates/vm"] diff --git a/agent/crates/vm/Cargo.toml b/agent/crates/vm/Cargo.toml new file mode 100644 index 0000000..280a769 --- /dev/null +++ b/agent/crates/vm/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "dlx-vm" +version = "0.1.0" +edition = "2024" + +[dependencies] +thiserror = "2.0.14" diff --git a/agent/crates/vm/src/error.rs b/agent/crates/vm/src/error.rs new file mode 100644 index 0000000..7d1353f --- /dev/null +++ b/agent/crates/vm/src/error.rs @@ -0,0 +1,9 @@ +use thiserror::Error; + +use crate::qemu::error::QemuError; + +#[derive(Debug, Clone, Error)] +pub enum VmError { + #[error("QEMU error")] + Qemu(#[from] QemuError), +} diff --git a/agent/crates/vm/src/lib.rs b/agent/crates/vm/src/lib.rs new file mode 100644 index 0000000..f76c4b3 --- /dev/null +++ b/agent/crates/vm/src/lib.rs @@ -0,0 +1,4 @@ +mod error; +mod model; +mod qemu; +mod service; diff --git a/agent/crates/vm/src/model.rs b/agent/crates/vm/src/model.rs new file mode 100644 index 0000000..698daf8 --- /dev/null +++ b/agent/crates/vm/src/model.rs @@ -0,0 +1,3 @@ +pub mod disk; +pub mod net; +pub mod vm; diff --git a/agent/crates/vm/src/model/disk.rs b/agent/crates/vm/src/model/disk.rs new file mode 100644 index 0000000..aa4ab8d --- /dev/null +++ b/agent/crates/vm/src/model/disk.rs @@ -0,0 +1,7 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Disk { + pub file: String, + pub index: u32, + pub format: Option, + pub media: String, +} diff --git a/agent/crates/vm/src/model/net.rs b/agent/crates/vm/src/model/net.rs new file mode 100644 index 0000000..b0dee6d --- /dev/null +++ b/agent/crates/vm/src/model/net.rs @@ -0,0 +1,2 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VmNet(pub String); diff --git a/agent/crates/vm/src/model/vm.rs b/agent/crates/vm/src/model/vm.rs new file mode 100644 index 0000000..51a121c --- /dev/null +++ b/agent/crates/vm/src/model/vm.rs @@ -0,0 +1,15 @@ +use crate::model::{disk::Disk, net::VmNet}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct MemoryMB(pub u32); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MachineAccel(pub String); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CloudInitVM { + pub mem: MemoryMB, + pub nets: Vec, + pub disks: Vec, + pub machine: MachineAccel, +} diff --git a/agent/crates/vm/src/qemu.rs b/agent/crates/vm/src/qemu.rs new file mode 100644 index 0000000..b4b9f26 --- /dev/null +++ b/agent/crates/vm/src/qemu.rs @@ -0,0 +1,4 @@ +pub mod config; +pub mod error; +pub mod param; +pub mod runner; diff --git a/agent/crates/vm/src/qemu/config.rs b/agent/crates/vm/src/qemu/config.rs new file mode 100644 index 0000000..30cf551 --- /dev/null +++ b/agent/crates/vm/src/qemu/config.rs @@ -0,0 +1,7 @@ +use crate::qemu::param::QemuParam; + +#[derive(Debug, Clone)] +pub struct QemuConfig { + pub executable: String, + pub param: QemuParam, +} diff --git a/agent/crates/vm/src/qemu/error.rs b/agent/crates/vm/src/qemu/error.rs new file mode 100644 index 0000000..b623d8c --- /dev/null +++ b/agent/crates/vm/src/qemu/error.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive(Debug, Clone, Error)] +pub enum QemuError { + #[error("unknown qemu error")] + Unknown, +} diff --git a/agent/crates/vm/src/qemu/param.rs b/agent/crates/vm/src/qemu/param.rs new file mode 100644 index 0000000..3d8473c --- /dev/null +++ b/agent/crates/vm/src/qemu/param.rs @@ -0,0 +1,158 @@ +use crate::model::{ + disk::Disk, + net::VmNet, + vm::{CloudInitVM, MachineAccel, MemoryMB}, +}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct QemuParam(pub String); + +pub trait IntoQemuParam { + fn into_qemu_param(self) -> QemuParam; +} + +impl IntoQemuParam for MachineAccel { + fn into_qemu_param(self) -> QemuParam { + QemuParam(format!("-machine accel={}", self.0)) + } +} + +impl IntoQemuParam for MemoryMB { + fn into_qemu_param(self) -> QemuParam { + QemuParam(format!("-m {}", self.0)) + } +} + +impl IntoQemuParam for VmNet { + fn into_qemu_param(self) -> QemuParam { + QemuParam(format!("-net {}", self.0)) + } +} + +impl IntoQemuParam for Disk { + fn into_qemu_param(self) -> QemuParam { + let mut v: Vec = vec![]; + v.push(format!("file={}", self.file)); + v.push(format!("index={}", self.index)); + if let Some(format) = self.format { + v.push(format!("format={}", format)); + } + v.push(format!("media={}", self.media)); + + let v = v.join(","); + + QemuParam(format!("-drive {}", v)) + } +} + +impl IntoQemuParam for CloudInitVM { + fn into_qemu_param(self) -> QemuParam { + let mut params: Vec = vec![]; + + params.push(self.mem.into_qemu_param()); + + let nets: Vec = self + .nets + .into_iter() + .map(IntoQemuParam::into_qemu_param) + .collect(); + params.extend_from_slice(&nets); + + let disks: Vec = self + .disks + .into_iter() + .map(IntoQemuParam::into_qemu_param) + .collect(); + params.extend_from_slice(&disks); + + params.push(self.machine.into_qemu_param()); + + let param = params + .into_iter() + .map(|e| e.0) + .collect::>() + .join(" "); + + QemuParam(param) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + model::{ + disk::Disk, + net::VmNet, + vm::{CloudInitVM, MachineAccel, MemoryMB}, + }, + qemu::param::{IntoQemuParam, QemuParam}, + }; + + #[test] + fn test_into_qemu_param_disk_format() { + let disk = Disk { + file: "f".into(), + index: 1, + format: Some("123".into()), + media: "m".into(), + }; + + let param = disk.into_qemu_param(); + assert_eq!( + param, + QemuParam("-drive file=f,index=1,format=123,media=m".into()) + ) + } + + #[test] + fn test_into_qemu_param_disk_no_format() { + let disk = Disk { + file: "f".into(), + index: 1, + format: None, + media: "m".into(), + }; + + let param = disk.into_qemu_param(); + assert_eq!(param, QemuParam("-drive file=f,index=1,media=m".into())) + } + + #[test] + fn test_into_qemu_param_mem() { + let param = MemoryMB(1024).into_qemu_param(); + assert_eq!(param, QemuParam("-m 1024".into())); + } + + #[test] + fn test_into_qemu_param_machine() { + let param = MachineAccel("kvm:tcg".into()).into_qemu_param(); + assert_eq!(param, QemuParam("-machine accel=kvm:tcg".into())); + } + + #[test] + fn test_into_qemu_param_net() { + let param = VmNet("nic".into()).into_qemu_param(); + assert_eq!(param, QemuParam("-net nic".into())); + } + + #[test] + fn test_into_qemu_param_vm() { + let vm = CloudInitVM { + mem: MemoryMB(1024), + nets: vec![VmNet("1".into()), VmNet("2".into())], + disks: vec![Disk { + file: "f".into(), + index: 1, + format: None, + media: "m".into(), + }], + machine: MachineAccel("kvm:tcg".into()), + }; + + let param = vm.into_qemu_param(); + assert_eq!( + param.0, + "-m 1024 -net 1 -net 2 -drive file=f,index=1,media=m -machine accel=kvm:tcg" + ); + } +} diff --git a/agent/crates/vm/src/qemu/runner.rs b/agent/crates/vm/src/qemu/runner.rs new file mode 100644 index 0000000..e4c2e51 --- /dev/null +++ b/agent/crates/vm/src/qemu/runner.rs @@ -0,0 +1,15 @@ +use std::process::Command; + +use crate::qemu::config::QemuConfig; + +pub fn run_qemu(config: &QemuConfig) { + let args: Vec = config + .param + .clone() + .0 + .split(" ") + .map(|s| s.to_string()) + .collect(); + + let handle = Command::new(&config.executable).args(&args).spawn(); +} diff --git a/agent/crates/vm/src/service.rs b/agent/crates/vm/src/service.rs new file mode 100644 index 0000000..748377a --- /dev/null +++ b/agent/crates/vm/src/service.rs @@ -0,0 +1 @@ +pub mod runner; diff --git a/agent/crates/vm/src/service/runner.rs b/agent/crates/vm/src/service/runner.rs new file mode 100644 index 0000000..5925b86 --- /dev/null +++ b/agent/crates/vm/src/service/runner.rs @@ -0,0 +1,2 @@ +pub trait RunnerService { +}