Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
57b8bcd
feat(axvisor): VM start/stop 控制 API(axum 异步 stop + lifecycle 自测)
aptacc2421 Aug 5, 2026
40d0115
fix(axvisor): 控制测试 guest 改用 registry 预置镜像,CI 可复现
aptacc2421 Aug 5, 2026
5d04171
fix(axvisor): start-on-stopped 显式 409,stop 响应标注 async
aptacc2421 Aug 5, 2026
d061561
feat(net): 启用 ArceOS 网络栈、SMP=2 与 vCPU 核隔离
aptacc2421 Aug 3, 2026
0ae812d
feat(axvisor): 新增 axum 只读 HTTP 管理 API(GET /api/vms)
aptacc2421 Aug 5, 2026
ab1ca7c
fix(axvisor): explicit tower util feature and rename http::axum to se…
aptacc2421 Aug 5, 2026
36c84dc
test(axvm): add deterministic vCPU affinity mapping tests
aptacc2421 Aug 6, 2026
c044e2a
docs(axvisor): drop stale plan-doc reference from http-axum feature c…
aptacc2421 Aug 6, 2026
bc06093
docs(axvisor): 新增管理 HTTP 控制面 QEMU hostfwd + curl 操作指南
aptacc2421 Aug 5, 2026
9e3da81
docs(axvisor): 完善 HTTP 控制面 quickstart 可复现性与 NIC 排查
aptacc2421 Aug 5, 2026
fd8f719
feat(axvisor): 运行时 create/delete VM 控制 API(受限 create)
aptacc2421 Aug 5, 2026
cb58b92
fix(axvisor): dynamic 测试 fail_regex 补抓 control lifecycle FAILED
aptacc2421 Aug 5, 2026
7d4fca9
fix(axvm): vcpu_run 日志改用 host::cpu::current_id 避免裸 ax_hal 引用
aptacc2421 Aug 6, 2026
a29d45c
fix(axvisor): readonly HTTP 自测单哨兵 + x86_64 用例可发现
aptacc2421 Aug 7, 2026
5b6e3fc
feat(axvisor): QEMU hostfwd host→guest TCP 集成测试(http-axum-tcp)
aptacc2421 Aug 7, 2026
18dfb51
fix(axvisor): 移除误带入 PR1 的 web-ui 骨架并清理 host_probe 导入
aptacc2421 Aug 7, 2026
e71f62b
feat(axvisor): 管理 HTTP 控制面认证 + 默认 loopback 绑定 + 真实 TCP 拒绝访问回归
aptacc2421 Aug 8, 2026
b357c43
refactor(axvisor): 移除内核内 self-test,改用 host probe + fs Linux guest
aptacc2421 Aug 10, 2026
c763300
refactor(axbuild): host_http_probe 场景 Rust 代码改为 case 目录 probe.sh 脚本
aptacc2421 Aug 11, 2026
fd59db9
docs(axvisor): 将 HTTP 控制面 quickstart 文档移出仓库跟踪,保留本地副本
aptacc2421 Aug 11, 2026
9203a3b
test(axvisor): 归并 HTTP 测试目录为共享 wrapper 并按 fs 注册 Linux guest
aptacc2421 Aug 12, 2026
985d8be
chore(repo): rebase 后重新生成 Cargo.lock 补齐 axvisor 可选依赖
aptacc2421 Aug 12, 2026
cfbbc13
chore(axbuild): cargo fmt 修复 host probe 守卫与 qemu runner 排版
aptacc2421 Aug 12, 2026
1bb4a8d
feat(axvisor): bridge the guest L2 switch to a host NIC uplink
aptacc2421 Aug 12, 2026
34d46e4
merge upstream dev into axvisor-pr1-axum-infra
aptacc2421 Aug 12, 2026
c39a642
fix(axruntime): cfg-split net device collection to satisfy clippy
aptacc2421 Aug 12, 2026
aab2a56
refactor(axvisor): remove net-uplink from the HTTP control-plane PR
aptacc2421 Aug 13, 2026
46ff4b0
merge upstream dev into axvisor-pr1-axum-infra
aptacc2421 Aug 13, 2026
1e86e2d
refactor(axvisor): converge HTTP control-plane testing per review
aptacc2421 Aug 13, 2026
f4f03f5
chore(axbuild): rustfmt http_probe and case types
aptacc2421 Aug 13, 2026
6401567
fix(axbuild): preserve real serial failures over host probe verdict
aptacc2421 Aug 13, 2026
5f180f4
refactor(axbuild): drop probe SIGKILL, require QEMU + probe both pass
aptacc2421 Aug 13, 2026
dbf00f2
fix(axbuild): keep QMP quit connection open so QEMU exits after a probe
aptacc2421 Aug 14, 2026
00d7836
feat(axbuild): extend host probe to the full management-plane contract
aptacc2421 Aug 14, 2026
e83afba
refactor(axbuild): move HTTP probe config and guard to the axvisor layer
aptacc2421 Aug 14, 2026
9b9643c
test(axbuild): cover the axvisor host_http_probe toml parsing path
aptacc2421 Aug 14, 2026
428c4cd
fix(axvm): wait for vCPU entry before accepting request-stop
aptacc2421 Aug 14, 2026
061039f
chore(ci): retrigger CI run on branch head
aptacc2421 Aug 14, 2026
ca6849b
chore(ci): retrigger CI run on branch head (timer-stress flake)
aptacc2421 Aug 14, 2026
6754937
refactor(axbuild): move axvisor HTTP probe contract into the case asset
aptacc2421 Aug 14, 2026
182e514
fix(axvisor): retry probe request on boot-window read timeout
aptacc2421 Aug 14, 2026
4300010
chore(ci): retrigger CI run on branch head
aptacc2421 Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,18 @@ jobs:
container_image: base
limit_to_owner: ""
main_pr_only: false
- name: Test axvisor aarch64 qemu (http control-plane)
use_container: false
runs_on: '["self-hosted","linux","qcs"]'
self_hosted_owner: rcore-os
timeout_minutes: 30
command: |
cargo xtask image pull qemu-aarch64 --output-dir tmp/axbuild/images
cargo xtask axvisor test qemu --arch aarch64 --test-case http-control-plane
cache_key: ""
container_image: base
limit_to_owner: ""
main_pr_only: false
- name: Test axvisor aarch64 qemu (panic modes)
use_container: false
runs_on: '["self-hosted","linux","qcs"]'
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

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

17 changes: 17 additions & 0 deletions os/axvisor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ stack-protector = ["ax-std/stack-protector"]
backtrace = ["ax-std/backtrace", "dep:axbacktrace"]
test-backtrace-panic = ["backtrace"]
test-panic-no-backtrace = ["dep:axbacktrace"]
# axum-based management HTTP server (see src/http/ for the Router).
# Off by default. Replaces the hand-rolled pilot, which is intentionally not
# carried forward.
# axum-based management HTTP server. Pulls `ax-std/net` explicitly so the
# ArceOS network stack (which probes the QEMU virtio-net NIC and backs the
# tokio `TcpListener`) is enabled only when the control plane is built; a
# non-HTTP build must not enable the network subsystem.
http-axum = ["ax-std/net", "dep:axum", "dep:tokio", "dep:serde_json"]
# Do not auto-boot the default VMs at startup; the HTTP control plane starts
# and stops them on demand (VMs are created and stay in `Ready`).
no-auto-start = []

[dependencies]
shlex.workspace = true
Expand All @@ -57,6 +68,12 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(axtest)', 'cfg(feature, va
[target.'cfg(any(not(any(windows, unix)), target_env = "musl"))'.dependencies]
anyhow.workspace = true
log = "0.4"
axum = { version = "0.8", optional = true }
serde_json = { version = "1", optional = true }
# The runtime only enables the IO driver (`enable_io()`), so no `time` driver
# and thus no `timerfd` syscall is needed. `rt` + `net` cover the manually
# built current-thread runtime and `TcpListener`.
tokio = { version = "1", optional = true, features = ["rt", "net"] }

# System dependent modules provided by ArceOS.
ax-api.workspace = true
Expand Down
10 changes: 9 additions & 1 deletion os/axvisor/src/guest_console/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ mod host;
mod mux;

pub(crate) use host::{configure_host_console_reader, read_host_byte, wait_for_host_input};
#[cfg_attr(
feature = "no-auto-start",
expect(
unused_imports,
reason = "only the auto-start boot path attaches the console to a default running VM"
)
)]
pub(crate) use mux::attach_default;
pub(crate) use mux::{
ConsoleInputEvent, activate, attach, attach_default, attached_vm, mark_running, mark_stopped,
ConsoleInputEvent, activate, attach, attached_vm, mark_running, mark_stopped,
reconcile_vm_states, remove, route_host_byte, serial_backend_factory,
};
7 changes: 7 additions & 0 deletions os/axvisor/src/guest_console/mux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,13 @@ pub fn route_host_byte(byte: u8) -> ConsoleInputEvent {
}

/// Attach the lowest-ID member of the default running VM set.
#[cfg_attr(
feature = "no-auto-start",
expect(
dead_code,
reason = "only the auto-start boot path attaches the console to a default running VM"
)
)]
pub fn attach_default(running: impl IntoIterator<Item = VMId>) -> Option<VMId> {
GUEST_CONSOLE_MUX.attach_default(running)
}
Expand Down
63 changes: 63 additions & 0 deletions os/axvisor/src/http/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//! Bearer-token access control for the management HTTP control plane.
//!
//! Mutating routes (`create`/`delete`/`start`/`stop`) require an
//! `Authorization: Bearer <token>` header matching the build-time token. The
//! token is baked into the image at
//! build time from the `[env] AXVM_HTTP_TOKEN` build-config variable — the same
//! `option_env!` mechanism `crate::shell::command::base` uses for `AX_ARCH`.
//!
//! The control plane is **deny-by-default**: if `AXVM_HTTP_TOKEN` is unset,
//! every protected route returns `401` and cannot be used. There is no
//! "fall back to allowing writes without a token" path — a build that forgets
//! the token fails its tests instead of silently exposing EL2 state changes.
//! Read-only routes (`GET`) are intentionally left open; they expose no state
//! mutation, and the default loopback bind (see [`crate::http::server`]) keeps
//! them off the management network unless an operator explicitly opts in.

use axum::{
extract::FromRequestParts,
http::{
StatusCode,
header::{AUTHORIZATION, HeaderValue},
},
};

/// A request that carries a matching `Authorization: Bearer <token>` header.
///
/// Attach as the first extractor on a mutating handler. Rejects the request
/// with `401 Unauthorized` when no token was baked into the image
/// (`AXVM_HTTP_TOKEN` unset) or the header is missing / does not match.
pub struct ApiToken;

impl ApiToken {
/// Whether the given header value carries the required bearer token.
fn header_matches(value: &HeaderValue) -> bool {
let Some(token) = option_env!("AXVM_HTTP_TOKEN") else {
return false;
};
value.to_str().ok().is_some_and(|value| {
value
.strip_prefix("Bearer ")
.is_some_and(|rest| rest == token)
})
}
}

impl<S: Sync> FromRequestParts<S> for ApiToken {
type Rejection = StatusCode;

async fn from_request_parts(
parts: &mut axum::http::request::Parts,
_state: &S,
) -> Result<Self, Self::Rejection> {
let authorized = parts
.headers
.get(AUTHORIZATION)
.is_some_and(Self::header_matches);
if authorized {
Ok(ApiToken)
} else {
Err(StatusCode::UNAUTHORIZED)
}
}
}
25 changes: 25 additions & 0 deletions os/axvisor/src/http/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! Management HTTP control plane.
//!
//! Served by an axum `Router` running on a tokio current-thread runtime
//! (see [`server`]). The VM list/detail and start/stop lifecycle routes live
//! in [`vm`]. JSON is built with `serde_json`.
//!
//! Security boundary: mutating routes require a build-time bearer token
//! ([`auth`]); the server binds `127.0.0.1:8080` by default and only binds
//! wider when `[env] AXVM_HTTP_BIND` opts in. See the per-module docs.
//!
//! This whole module is only compiled under the `http-axum` feature, which is
//! off by default. The hand-rolled HTTP/1.0 pilot was intentionally not
//! carried forward.

pub mod auth;
pub mod server;
pub mod vm;

/// Blocking entry point for the management HTTP server.
///
/// Spawned on its own task (see `crate::main`); builds the tokio runtime and
/// serves until the hypervisor shuts down.
pub fn serve() {
server::serve();
}
67 changes: 67 additions & 0 deletions os/axvisor/src/http/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! axum-based management HTTP server (`http-axum` feature).
//!
//! Runs an axum `Router` on a tokio current-thread runtime and serves the
//! management API. Routes and JSON fields mirror the hand-rolled pilot's API,
//! but dispatch and JSON construction are delegated to axum + serde_json.
//!
//! ```text
//! GET /api/vms → 200, JSON array (summary form)
//! GET /api/vms/{id} → 200, JSON detail (with vcpu_states) | 404
//! POST /api/vms/create → 200 {"id":N} | 400 | 409 | 500 (body {"toml": "..."})
//! DELETE /api/vms/{id} → 204 | 404 | 500
//! POST /api/vms/{id}/start → 200 {"ok":true,"status":...} | 404 | 409 | 503
//! POST /api/vms/{id}/stop → 200 {"ok":true,"status":...} | 404 | 409 | 503
//! ```
//!
//! Mutating routes (`create`/`delete`/`start`/`stop`) require
//! `Authorization: Bearer <token>` with the build-time `[env] AXVM_HTTP_TOKEN`;
//! see [`crate::http::auth`]. GET routes are open. The listener binds
//! [`bind_addr`], loopback by default.
//!
//! The tokio reactor is initialized with `enable_io()` only (no time driver),
//! which needs only epoll, so no `timerfd` syscall is required.

use axum::{Router, routing::get, routing::post};

use crate::http::vm;

/// Assemble the management routes.
pub fn router() -> Router {
Router::new()
.route("/api/vms", get(vm::list_vms))
.route("/api/vms/{id}", get(vm::vm_detail).delete(vm::vm_delete))
.route("/api/vms/create", post(vm::vm_create))
.route("/api/vms/{id}/start", post(vm::vm_start))
.route("/api/vms/{id}/stop", post(vm::vm_stop))
}

/// Bind address for the management HTTP server.
///
/// Defaults to loopback (`127.0.0.1:8080`) so a stock `http-axum` build is not
/// reachable from the management network. Test/dev flows that need QEMU
/// hostfwd to reach the in-guest listener must opt in to all interfaces by
/// setting `[env] AXVM_HTTP_BIND = "0.0.0.0:8080"` in their build config; the
/// mutating routes still require the bearer token regardless of the bind.
fn bind_addr() -> &'static str {
option_env!("AXVM_HTTP_BIND").unwrap_or("127.0.0.1:8080")
}

/// Blocking serve: build a tokio current-thread runtime and hand it to axum.
///
/// `main` spawns this on its own task via `std::thread::spawn(|| http::serve())`;
/// the runtime is built here. Only the IO driver is enabled — the epoll
/// reactor suffices for `axum::serve`; a time driver would need `timerfd`.
pub fn serve() {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_io()
.build()
.expect("failed to build tokio runtime");
rt.block_on(async {
let bind = bind_addr();
let listener = tokio::net::TcpListener::bind(bind)
.await
.expect("failed to bind management HTTP server");
info!("management HTTP server (axum) listening on {bind}");
axum::serve(listener, router()).await.expect("server error");
Comment thread
ZR233 marked this conversation as resolved.
});
}
Loading
Loading