Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 1 addition & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,7 @@ For R2, add `--storage-backend r2 --region auto --r2-account-id <account-id>` to

## Attach With `nbd-client`

The server speaks fixed-newstyle NBD and supports `OPT_GO`.

- in direct mode, there is one export named exactly like `--export-id`
- in `serve` mode, the export name is the managed `export_id`
The server speaks fixed-newstyle NBD and supports `OPT_GO`. The NBD export name is the managed `export_id`.

```bash
sudo modprobe nbd max_part=8
Expand Down Expand Up @@ -202,9 +199,6 @@ curl -X POST --unix-socket /tmp/nbd-server.sock \
`volume.json` stores the current published snapshot id for the export. The manifest path is inferred from that snapshot id.
Older snapshot objects are not deleted automatically when a new snapshot or compaction is published.

## Direct Single-Export Modes

`create`, `open`, and `clone` still exist as compatibility commands for one-export workflows and tests. Those commands require explicit `--cache-dir`, `--prefix`, and storage flags because they bypass the multi-export manager entirely.

## Recovery

Expand Down
12 changes: 3 additions & 9 deletions design/multi-export-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,11 @@ The storage backend remains process-global and is configured at startup just lik

On startup, the server scans the global export root and loads all discovered `volume.json` objects into the registry.

### Backward compatibility
### CLI surface

For the first implementation, keep the existing direct modes:
The runtime CLI should expose only `serve`.

- `create`
- `open`
- `clone`

Those modes remain useful for tests and simple one-export workflows.

`serve` is the new multi-export path.
Per-volume lifecycle operations such as create, open, clone, snapshot, compact, and cache reset are handled through the admin API rather than through separate process modes.

## Remote Volume Metadata

Expand Down
59 changes: 0 additions & 59 deletions src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,8 @@ use serde::Serialize;
use tokio::net::UnixListener;

use crate::error::Error;
use crate::export::Export;
use crate::manager::{CloneExportRequest, CreateExportRequest, ExportManager, OpenExportRequest};

#[derive(Clone)]
struct AdminState {
export: Arc<Export>,
}

#[derive(Clone)]
struct ManagerAdminState {
manager: Arc<ExportManager>,
Expand All @@ -29,22 +23,6 @@ struct ErrorBody {
error: String,
}

pub async fn serve_admin(socket_path: &Path, export: Arc<Export>) -> crate::Result<()> {
if socket_path.exists() {
std::fs::remove_file(socket_path)?;
}
let listener = UnixListener::bind(socket_path)?;
let router = Router::new()
.route("/v1/status", get(get_status))
.route("/v1/snapshot", post(post_snapshot))
.route("/v1/compact", post(post_compact))
.route("/v1/cache/reset", post(post_reset_cache))
.with_state(AdminState { export });
axum::serve(listener, router)
.await
.map_err(crate::Error::Io)
}

pub async fn serve_manager_admin(
socket_path: &Path,
manager: Arc<ExportManager>,
Expand Down Expand Up @@ -75,21 +53,6 @@ pub async fn serve_manager_admin(
.map_err(crate::Error::Io)
}

async fn get_status(State(state): State<AdminState>) -> Json<crate::export::Status> {
Json(state.export.status().await)
}

async fn post_snapshot(
State(state): State<AdminState>,
) -> std::result::Result<Json<crate::export::SnapshotResponse>, (StatusCode, Json<ErrorBody>)> {
state
.export
.snapshot()
.await
.map(Json)
.map_err(error_response)
}

async fn get_exports(
State(state): State<ManagerAdminState>,
) -> Json<Vec<crate::manager::ExportSummary>> {
Expand Down Expand Up @@ -156,17 +119,6 @@ async fn get_export_status(
.map_err(error_response)
}

async fn post_compact(
State(state): State<AdminState>,
) -> std::result::Result<Json<crate::export::CompactResponse>, (StatusCode, Json<ErrorBody>)> {
state
.export
.compact()
.await
.map(Json)
.map_err(error_response)
}

async fn post_export_snapshot(
AxumPath(export_id): AxumPath<String>,
State(state): State<ManagerAdminState>,
Expand All @@ -191,17 +143,6 @@ async fn post_export_compact(
.map_err(error_response)
}

async fn post_reset_cache(
State(state): State<AdminState>,
) -> std::result::Result<Json<crate::export::ResetCacheResponse>, (StatusCode, Json<ErrorBody>)> {
state
.export
.reset_cache()
.await
.map(Json)
.map_err(error_response)
}

async fn post_export_reset_cache(
AxumPath(export_id): AxumPath<String>,
State(state): State<ManagerAdminState>,
Expand Down
123 changes: 3 additions & 120 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,81 +1,15 @@
use std::net::SocketAddr;
use std::path::PathBuf;

use clap::{Parser, Subcommand, ValueEnum};
use clap::{Parser, ValueEnum};

pub const CHUNK_SIZE: u64 = 4 * 1024 * 1024;

#[derive(Debug, Clone, Parser)]
#[command(author, version, about)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}

#[derive(Debug, Clone, Subcommand)]
pub enum Commands {
Create(ServerConfigArgs),
Open(ServerConfigArgs),
Clone(CloneConfigArgs),
Serve(ServeConfigArgs),
}

#[derive(Debug, Clone, Parser)]
pub struct ServerConfigArgs {
#[arg(long)]
pub export_id: String,
#[arg(long)]
pub cache_dir: PathBuf,
#[arg(long)]
pub bucket: String,
#[arg(long)]
pub prefix: String,
#[arg(long)]
pub listen: SocketAddr,
#[arg(long)]
pub admin_sock: PathBuf,
#[arg(long, value_enum, default_value_t = StorageBackendKind::S3)]
pub storage_backend: StorageBackendKind,
#[arg(long, default_value = "us-east-1")]
pub region: String,
#[arg(long)]
pub endpoint_url: Option<String>,
#[arg(long)]
pub r2_account_id: Option<String>,
#[arg(long, default_value_t = CHUNK_SIZE)]
pub chunk_size: u64,
#[arg(long)]
pub snapshot_id: Option<String>,
#[arg(long)]
pub size: Option<u64>,
}

#[derive(Debug, Clone, Parser)]
pub struct CloneConfigArgs {
#[arg(long)]
pub export_id: String,
#[arg(long)]
pub cache_dir: PathBuf,
#[arg(long)]
pub bucket: String,
#[arg(long)]
pub prefix: String,
#[arg(long)]
pub source_prefix: String,
#[arg(long)]
pub source_snapshot_id: Option<String>,
#[arg(long)]
pub listen: SocketAddr,
#[arg(long)]
pub admin_sock: PathBuf,
#[arg(long, value_enum, default_value_t = StorageBackendKind::S3)]
pub storage_backend: StorageBackendKind,
#[arg(long, default_value = "us-east-1")]
pub region: String,
#[arg(long)]
pub endpoint_url: Option<String>,
#[arg(long)]
pub r2_account_id: Option<String>,
#[command(flatten)]
pub serve: ServeConfigArgs,
}

#[derive(Debug, Clone, Parser)]
Expand Down Expand Up @@ -148,57 +82,6 @@ pub struct ServerConfig {
pub volume_key: Option<String>,
}

impl From<ServerConfigArgs> for ServerConfig {
fn from(value: ServerConfigArgs) -> Self {
Self {
export_id: value.export_id,
cache_dir: value.cache_dir,
storage: StorageConfig {
backend: value.storage_backend,
bucket: value.bucket,
prefix: value.prefix.trim_end_matches('/').to_string(),
region: value.region,
endpoint_url: value.endpoint_url,
r2_account_id: value.r2_account_id,
},
listen: value.listen,
admin_sock: value.admin_sock,
chunk_size: value.chunk_size,
snapshot_id: value.snapshot_id,
image_size: value.size,
clone_source: None,
volume_key: None,
}
}
}

impl From<CloneConfigArgs> for ServerConfig {
fn from(value: CloneConfigArgs) -> Self {
Self {
export_id: value.export_id,
cache_dir: value.cache_dir,
storage: StorageConfig {
backend: value.storage_backend,
bucket: value.bucket,
prefix: value.prefix.trim_end_matches('/').to_string(),
region: value.region,
endpoint_url: value.endpoint_url,
r2_account_id: value.r2_account_id,
},
listen: value.listen,
admin_sock: value.admin_sock,
chunk_size: CHUNK_SIZE,
snapshot_id: None,
image_size: None,
clone_source: Some(CloneSourceConfig {
prefix: value.source_prefix.trim_end_matches('/').to_string(),
snapshot_id: value.source_snapshot_id,
}),
volume_key: None,
}
}
}

impl From<ServeConfigArgs> for ServeConfig {
fn from(value: ServeConfigArgs) -> Self {
Self {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ pub mod nbd;
pub mod remote;
pub mod volume;

pub use config::{Cli, Commands};
pub use config::Cli;
pub use error::{Error, Result};
Loading
Loading