Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
13c066d
fix: report stopped status for stacks with all-stopped containers
CodeMaster4711 Aug 16, 2026
fe06ba8
style: apply cargo fmt and clippy fixes
github-actions[bot] Aug 16, 2026
c20964c
feat: add data model for object storage buckets and garage nodes
CodeMaster4711 Aug 16, 2026
cd84454
feat: add object-storage service with bucket crud
CodeMaster4711 Aug 16, 2026
f1a65b2
feat: add bucket access key issuance and rotation
CodeMaster4711 Aug 16, 2026
c6a7ca2
feat: add garage cluster reconciliation with dynamic replication factor
CodeMaster4711 Aug 16, 2026
c62c7d6
feat: wire object-storage into api-gateway with rbac and tenant-scope…
CodeMaster4711 Aug 16, 2026
630dedc
feat: add rg-internal s3 dns and dnat wiring in agent
CodeMaster4711 Aug 16, 2026
11095dd
feat: add sigv4-preserving s3 streaming proxy for external buckets
CodeMaster4711 Aug 16, 2026
834144f
feat: inject bucket credentials into workload env on bucket binding
CodeMaster4711 Aug 16, 2026
7be4450
feat: add bucket tab to resource group dashboard
CodeMaster4711 Aug 16, 2026
2add4b1
fix: add object-storage crate to docker build and compose
CodeMaster4711 Aug 16, 2026
a8739e2
fix: reload hypervisor version reactively once auth token hydrates
CodeMaster4711 Aug 16, 2026
19cbb00
fix: add object-storage crate to control-plane docker build
CodeMaster4711 Aug 16, 2026
ac451d7
feat: consolidate add bucket into add resource picker and add buckets…
CodeMaster4711 Aug 16, 2026
f0e6738
fix: pass rg_dns_registry as arc to heartbeat loop
CodeMaster4711 Aug 16, 2026
6b7d93a
feat: add garage dev container for local object storage testing
CodeMaster4711 Aug 16, 2026
0110aa3
fix: use valid 32-byte hex rpc secret for dev garage container
CodeMaster4711 Aug 16, 2026
84b394e
fix: use garage binary for healthcheck instead of missing wget
CodeMaster4711 Aug 16, 2026
2af50c2
feat: add s3 object browser with presigned upload and download
CodeMaster4711 Aug 16, 2026
e8c70fa
fix: route presigned s3 uploads through dedicated gateway port
CodeMaster4711 Aug 16, 2026
077a2a0
fix: register single-node garage deployments without a csfx agent
CodeMaster4711 Aug 16, 2026
d41bda1
fix: correct sigv4 encoding and garage node status tracking
CodeMaster4711 Aug 16, 2026
62000bd
style: apply cargo fmt and clippy fixes
github-actions[bot] Aug 16, 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
302 changes: 267 additions & 35 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"control-plane/failover-controller",
"control-plane/sdn-controller",
"control-plane/volume-manager",
"control-plane/object-storage",
"control-plane/registry",
"control-plane/shared/entity",
"control-plane/shared/migration",
Expand Down
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM rust:1.88-slim-bookworm AS base
FROM rust:1.97-slim-bookworm AS base

WORKDIR /app

Expand Down Expand Up @@ -26,6 +26,7 @@ COPY control-plane/scheduler/Cargo.toml ./control-plane/scheduler/
COPY control-plane/failover-controller/Cargo.toml ./control-plane/failover-controller/
COPY control-plane/sdn-controller/Cargo.toml ./control-plane/sdn-controller/
COPY control-plane/volume-manager/Cargo.toml ./control-plane/volume-manager/
COPY control-plane/object-storage/Cargo.toml ./control-plane/object-storage/
COPY control-plane/registry/Cargo.toml ./control-plane/registry/
COPY control-plane/shared/entity/Cargo.toml ./control-plane/shared/entity/
COPY control-plane/shared/migration/Cargo.toml ./control-plane/shared/migration/
Expand All @@ -39,6 +40,7 @@ RUN mkdir -p agent/src \
control-plane/failover-controller/src \
control-plane/sdn-controller/src \
control-plane/volume-manager/src \
control-plane/object-storage/src \
control-plane/registry/src \
control-plane/shared/entity/src \
control-plane/shared/migration/src \
Expand All @@ -51,6 +53,7 @@ RUN mkdir -p agent/src \
&& echo "fn main() {}" > control-plane/failover-controller/src/main.rs \
&& echo "fn main() {}" > control-plane/sdn-controller/src/main.rs \
&& echo "fn main() {}" > control-plane/volume-manager/src/main.rs \
&& echo "fn main() {}" > control-plane/object-storage/src/main.rs \
&& echo "fn main() {}" > control-plane/registry/src/main.rs \
&& echo "fn main() {}" > control-plane/csfx-migrate/src/main.rs \
&& echo "fn main() {}" > control-plane/csfx-updater/src/main.rs \
Expand Down
5 changes: 1 addition & 4 deletions agent/src/firecracker/rootfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,7 @@ impl RootfsBuilder {
(Some(tag), None) => format!(":{tag}"),
(None, None) => String::new(),
};
let mirrored = format!(
"{mirror}/{}{tag_or_digest}",
reference.repository()
);
let mirrored = format!("{mirror}/{}{tag_or_digest}", reference.repository());
mirrored.parse().context("Invalid mirrored image reference")
}

Expand Down
18 changes: 14 additions & 4 deletions agent/src/firecracker/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use serde_json::json;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::UnixStream;
use tokio::process::Command;
Expand Down Expand Up @@ -153,16 +154,21 @@ struct GuestNetwork {
pub struct FirecrackerRuntime {
wg_private_key_b64: String,
dns_supervisor: RgDnsProcessSupervisor,
rg_dns_registry: Arc<crate::rg_dns::RgDnsRegistry>,
handles: Mutex<HashMap<String, VmHandle>>,
next_cid: Mutex<u32>,
reconciled: AtomicBool,
}

impl FirecrackerRuntime {
pub fn new(wg_private_key_b64: String) -> Self {
pub fn new(
wg_private_key_b64: String,
rg_dns_registry: Arc<crate::rg_dns::RgDnsRegistry>,
) -> Self {
Self {
wg_private_key_b64,
dns_supervisor: RgDnsProcessSupervisor::new(),
rg_dns_registry,
handles: Mutex::new(HashMap::new()),
next_cid: Mutex::new(next_free_cid_on_host()),
reconciled: AtomicBool::new(false),
Expand Down Expand Up @@ -194,9 +200,13 @@ impl FirecrackerRuntime {
resource_group_id: &str,
resource_group_cidr: Option<&str>,
) -> Result<String> {
let iface = crate::rg_network::ensure_bridge(resource_group_id, resource_group_cidr)
.await
.context("Failed to ensure resource group bridge")?;
let iface = crate::rg_network::ensure_bridge(
resource_group_id,
resource_group_cidr,
&self.rg_dns_registry,
)
.await
.context("Failed to ensure resource group bridge")?;

if let Some(cidr) = resource_group_cidr {
self.dns_supervisor
Expand Down
7 changes: 4 additions & 3 deletions agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,11 @@ async fn main() -> Result<()> {
warn!(error = %e, "Failed to initialize nftables resource group isolation");
}

let rg_dns_registry = Arc::new(rg_dns::RgDnsRegistry::new());

let firecracker_runtime = Arc::new(firecracker::runtime::FirecrackerRuntime::new(
wg_identity.private_key_b64.clone(),
Arc::clone(&rg_dns_registry),
));

let running_containers: Arc<Mutex<HashMap<String, String>>> =
Expand All @@ -153,8 +156,6 @@ async fn main() -> Result<()> {
let service_dns_registry: Arc<Mutex<HashMap<String, (String, String)>>> =
Arc::new(Mutex::new(HashMap::new()));

let rg_dns_registry = rg_dns::RgDnsRegistry::new();

if let Some(port) = std::env::var("CSFX_AGENT_PORT")
.ok()
.and_then(|v| v.parse::<u16>().ok())
Expand Down Expand Up @@ -363,7 +364,7 @@ async fn run_heartbeat_loop(
mounted_volumes: Arc<Mutex<HashMap<String, String>>>,
restart_counts: Arc<Mutex<HashMap<String, u32>>>,
service_dns_registry: Arc<Mutex<HashMap<String, (String, String)>>>,
rg_dns_registry: rg_dns::RgDnsRegistry,
rg_dns_registry: Arc<rg_dns::RgDnsRegistry>,
mut assignment_signal: tokio::sync::mpsc::UnboundedReceiver<()>,
) {
let mut interval = tokio::time::interval(Duration::from_secs(interval_secs));
Expand Down
24 changes: 24 additions & 0 deletions agent/src/nftables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,30 @@ pub async fn add_rg_port_dnat(
.await
}

pub async fn dnat_bridge_port(bridge_name: &str, rg_gateway_ip: &str, port: u16) -> Result<()> {
run_nft(&[
"add",
"rule",
"ip",
NAT_TABLE_NAME,
NAT_CHAIN_NAME,
"iifname",
bridge_name,
"ip",
"daddr",
rg_gateway_ip,
"tcp",
"dport",
&port.to_string(),
"dnat",
"to",
&format!("127.0.0.1:{}", port),
"comment",
&format!("\"{}-s3\"", bridge_name),
])
.await
}

pub async fn remove_node_port_rules(workload_id: &str) -> Result<()> {
let output = Command::new("nft")
.args(["-a", "list", "chain", "ip", NAT_TABLE_NAME, NAT_CHAIN_NAME])
Expand Down
23 changes: 21 additions & 2 deletions agent/src/rg_network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ use std::path::{Path, PathBuf};
use tokio::process::Command;
use tracing::info;

use crate::rg_dns::RgDnsRegistry;
use crate::spec::{rg_bridge_iface_name, second_host_ip};

const RG_REGISTRY_DIR: &str = "/var/lib/csfx-agent/rg-networks";

pub async fn ensure_bridge(resource_group_id: &str, cidr: Option<&str>) -> Result<String> {
const S3_SERVICE_NAME: &str = "s3";
const S3_DNAT_PORT: u16 = 3900;

pub async fn ensure_bridge(
resource_group_id: &str,
cidr: Option<&str>,
rg_dns_registry: &RgDnsRegistry,
) -> Result<String> {
let iface = rg_bridge_iface_name(resource_group_id);

write_registry_entry(resource_group_id).await?;
Expand All @@ -29,6 +36,18 @@ pub async fn ensure_bridge(resource_group_id: &str, cidr: Option<&str>) -> Resul
&iface,
])
.await?;

if let Err(e) = rg_dns_registry
.upsert(resource_group_id, S3_SERVICE_NAME, &gateway)
.await
{
info!(resource_group_id = %resource_group_id, error = %e, "Failed to register s3 dns record");
}

if let Err(e) = crate::nftables::dnat_bridge_port(&iface, &gateway, S3_DNAT_PORT).await
{
info!(resource_group_id = %resource_group_id, error = %e, "Failed to set up s3 dnat rule");
}
}
}

Expand Down
12 changes: 7 additions & 5 deletions agent/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,15 @@ async fn logs_handler(
})?;

info!(workload_id = %workload_id, container_id = %container_id, "opening log stream to guest");
let stream = futures_util::stream::once(async {
Ok::<_, std::io::Error>(axum::body::Bytes::new())
})
.chain(state.firecracker.logs(&container_id));
let stream =
futures_util::stream::once(async { Ok::<_, std::io::Error>(axum::body::Bytes::new()) })
.chain(state.firecracker.logs(&container_id));

Ok((
[(axum::http::header::CONTENT_TYPE, "text/plain; charset=utf-8")],
[(
axum::http::header::CONTENT_TYPE,
"text/plain; charset=utf-8",
)],
axum::body::Body::from_stream(stream),
))
}
Expand Down
Loading
Loading