Skip to content

Commit e2bd598

Browse files
committed
api: support hot reload of registry authentication
Add HTTP API endpoints to update and query daemon configuration at runtime without restarting nydusd. This is particularly useful for refreshing registry credentials when tokens expire. Key changes: - Add GET/PUT /api/v1/config endpoints for configuration management - Add config module in utils for centralized configuration updates - Add smoke test cases for hot reload configuration API - Update documentation with hot reload usage examples Signed-off-by: imeoer <yansong.ys@antgroup.com>
1 parent 67cd3d1 commit e2bd598

21 files changed

Lines changed: 948 additions & 74 deletions

File tree

Cargo.lock

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

api/src/http.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ pub struct DaemonConf {
5151
pub log_level: String,
5252
}
5353

54+
// Set/update global configuration.
55+
pub type Config = std::collections::HashMap<String, String>;
56+
5457
/// Identifier for cached blob objects.
5558
///
5659
/// Domains are used to control the blob sharing scope. All blobs associated with the same domain
@@ -106,6 +109,10 @@ pub enum ApiRequest {
106109
ExportFsFilesMetrics(Option<String>, bool),
107110
/// Get information about filesystem inflight requests.
108111
ExportFsInflightMetrics,
112+
/// Get global configuration.
113+
GetConfig(Option<String>),
114+
/// Update global configuration.
115+
UpdateConfig(Option<String>, Config),
109116

110117
// Nydus API v2
111118
/// Get daemon information excluding filesystem backends.
@@ -193,6 +200,8 @@ pub enum ApiResponsePayload {
193200
FsBackendInfo(String),
194201
// Filesystem Inflight Requests, v1.
195202
FsInflightMetrics(String),
203+
// Global configuration, v1.
204+
Config(Config),
196205

197206
/// List of blob objects, v2
198207
BlobObjectList(String),

api/src/http_endpoint_v1.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
88
use dbs_uhttp::{Method, Request, Response};
99

10-
use crate::http::{ApiError, ApiRequest, ApiResponse, ApiResponsePayload, HttpError};
10+
use crate::http::{ApiError, ApiRequest, ApiResponse, ApiResponsePayload, Config, HttpError};
1111
use crate::http_handler::{
1212
error_response, extract_query_part, parse_body, success_response, translate_status_code,
1313
EndpointHandler, HttpResult,
@@ -34,6 +34,10 @@ fn convert_to_response<O: FnOnce(ApiError) -> HttpError>(api_resp: ApiResponse,
3434
FsFilesPatterns(d) => success_response(Some(d)),
3535
FsBackendInfo(d) => success_response(Some(d)),
3636
FsInflightMetrics(d) => success_response(Some(d)),
37+
Config(conf) => {
38+
let json = serde_json::to_string(&conf).unwrap_or_else(|_| "{}".to_string());
39+
success_response(Some(json))
40+
}
3741
_ => panic!("Unexpected response message from API service"),
3842
}
3943
}
@@ -166,3 +170,28 @@ impl EndpointHandler for MetricsFsInflightHandler {
166170
}
167171
}
168172
}
173+
174+
/// Update global configuration of the daemon.
175+
pub struct ConfigHandler {}
176+
impl EndpointHandler for ConfigHandler {
177+
fn handle_request(
178+
&self,
179+
req: &Request,
180+
kicker: &dyn Fn(ApiRequest) -> ApiResponse,
181+
) -> HttpResult {
182+
match (req.method(), req.body.as_ref()) {
183+
(Method::Get, None) => {
184+
let id = extract_query_part(req, "id");
185+
let r = kicker(ApiRequest::GetConfig(id));
186+
Ok(convert_to_response(r, HttpError::Configure))
187+
}
188+
(Method::Put, Some(body)) => {
189+
let conf: Config = parse_body(body)?;
190+
let id = extract_query_part(req, "id");
191+
let r = kicker(ApiRequest::UpdateConfig(id, conf));
192+
Ok(convert_to_response(r, HttpError::Configure))
193+
}
194+
_ => Err(HttpError::BadRequest),
195+
}
196+
}
197+
}

api/src/http_handler.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ use crate::http_endpoint_common::{
2424
SendFuseFdHandler, StartHandler, TakeoverFuseFdHandler,
2525
};
2626
use crate::http_endpoint_v1::{
27-
FsBackendInfo, InfoHandler, MetricsFsAccessPatternHandler, MetricsFsFilesHandler,
28-
MetricsFsGlobalHandler, MetricsFsInflightHandler, HTTP_ROOT_V1,
27+
ConfigHandler, FsBackendInfo, InfoHandler, MetricsFsAccessPatternHandler,
28+
MetricsFsFilesHandler, MetricsFsGlobalHandler, MetricsFsInflightHandler, HTTP_ROOT_V1,
2929
};
3030
use crate::http_endpoint_v2::{BlobObjectListHandlerV2, InfoV2Handler, HTTP_ROOT_V2};
3131

@@ -158,6 +158,7 @@ lazy_static! {
158158
r.routes.insert(endpoint_v1!("/metrics/files"), Box::new(MetricsFsFilesHandler{}));
159159
r.routes.insert(endpoint_v1!("/metrics/inflight"), Box::new(MetricsFsInflightHandler{}));
160160
r.routes.insert(endpoint_v1!("/metrics/pattern"), Box::new(MetricsFsAccessPatternHandler{}));
161+
r.routes.insert(endpoint_v1!("/config"), Box::new(ConfigHandler{}));
161162

162163
// Nydus API, v2
163164
r.routes.insert(endpoint_v2!("/daemon"), Box::new(InfoV2Handler{}));

docs/nydusd.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,3 +454,48 @@ mnt
454454
├── pseudo_1
455455
└── pseudo_2
456456
```
457+
458+
### Hot Reload Configuration
459+
460+
Nydusd supports hot reloading of configuration without restarting the daemon. This is useful for updating credentials or other settings at runtime.
461+
462+
#### Update Configuration
463+
464+
To update configuration (e.g., registry authentication):
465+
466+
```shell
467+
curl --unix-socket /path/to/api.sock \
468+
-X PUT "http://localhost/api/v1/config?id=/" \
469+
-H "Content-Type: application/json" \
470+
-d '{
471+
"registry_auth": "<base64_encoded_auth>"
472+
}'
473+
```
474+
475+
#### Query Current Configuration
476+
477+
To retrieve the current configuration:
478+
479+
```shell
480+
curl --unix-socket /path/to/api.sock \
481+
-X GET "http://localhost/api/v1/config?id=/"
482+
```
483+
484+
Example response:
485+
486+
```json
487+
{
488+
"registry_auth": "<base64_encoded_auth>"
489+
}
490+
```
491+
492+
> **Note**: The `id` parameter specifies which mountpoint to configure. Use `/` for the root mountpoint or specify a sub-mountpoint path for multi-mount scenarios.
493+
494+
#### Supported Configuration Fields
495+
496+
The following fields can be updated via the hot reload API:
497+
498+
| Field | Description |
499+
| --------------- | ------------------------------------------------------------------------- |
500+
| `registry_auth` | Base64-encoded `username:password` credential for registry authentication |
501+

rafs/src/fs.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,25 +79,30 @@ pub struct Rafs {
7979

8080
impl Rafs {
8181
/// Create a new instance of `Rafs`.
82-
pub fn new(cfg: &Arc<ConfigV2>, id: &str, path: &Path) -> RafsResult<(Self, RafsIoReader)> {
82+
pub fn new(
83+
cfg: &Arc<ConfigV2>,
84+
mountpoint: &str,
85+
metadata_path: &Path,
86+
) -> RafsResult<(Self, RafsIoReader)> {
8387
// Assume all meta/data blobs are accessible, otherwise it will always cause IO errors.
8488
cfg.internal.set_blob_accessible(true);
8589

8690
let cache_cfg = cfg.get_cache_config().map_err(RafsError::LoadConfig)?;
8791
let rafs_cfg = cfg.get_rafs_config().map_err(RafsError::LoadConfig)?;
88-
let (sb, reader) = RafsSuper::load_from_file(path, cfg.clone(), false)
92+
let (sb, reader) = RafsSuper::load_from_file(metadata_path, cfg.clone(), false)
8993
.map_err(RafsError::FillSuperBlock)?;
9094
let blob_infos = sb.superblock.get_blob_infos();
91-
let device = BlobDevice::new(cfg, &blob_infos).map_err(RafsError::CreateDevice)?;
95+
let device =
96+
BlobDevice::new(cfg, &blob_infos, mountpoint).map_err(RafsError::CreateDevice)?;
9297

9398
if cfg.is_chunk_validation_enabled() && sb.meta.has_inlined_chunk_digest() {
9499
sb.superblock.set_blob_device(device.clone());
95100
}
96101

97102
let rafs = Rafs {
98-
id: id.to_string(),
103+
id: mountpoint.to_string(),
99104
device,
100-
ios: metrics::FsIoStats::new(id),
105+
ios: metrics::FsIoStats::new(mountpoint),
101106
sb: Arc::new(sb),
102107

103108
initialized: false,
@@ -140,7 +145,12 @@ impl Rafs {
140145
}
141146

142147
/// Update storage backend for blobs.
143-
pub fn update(&self, r: &mut RafsIoReader, conf: &Arc<ConfigV2>) -> RafsResult<()> {
148+
pub fn update(
149+
&self,
150+
r: &mut RafsIoReader,
151+
conf: &Arc<ConfigV2>,
152+
mountpoint: &str,
153+
) -> RafsResult<()> {
144154
info!("update");
145155
if !self.initialized {
146156
warn!("Rafs is not yet initialized");
@@ -159,7 +169,7 @@ impl Rafs {
159169
// step 2: update device (only localfs is supported)
160170
let blob_infos = self.sb.superblock.get_blob_infos();
161171
self.device
162-
.update(conf, &blob_infos, self.fs_prefetch)
172+
.update(conf, &blob_infos, self.fs_prefetch, mountpoint)
163173
.map_err(RafsError::SwapBackend)?;
164174
info!("update device is successful");
165175

rafs/src/metadata/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,7 +847,7 @@ impl RafsSuper {
847847
/// The `BlobDevice` object is needed to get meta information from RAFS V6 data blobs.
848848
pub fn create_blob_device(&self, config: Arc<ConfigV2>) -> Result<()> {
849849
let blobs = self.superblock.get_blob_infos();
850-
let device = BlobDevice::new(&config, &blobs)?;
850+
let device = BlobDevice::new(&config, &blobs, "/")?;
851851
self.superblock.set_blob_device(device);
852852
Ok(())
853853
}

service/src/blob_cache.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ impl DataBlob {
500500
pub fn new(config: &Arc<DataBlobConfig>) -> Result<Self> {
501501
let blob_id = config.blob_info().blob_id();
502502
let blob = BLOB_FACTORY
503-
.new_blob_cache(config.config_v2(), &config.blob_info)
503+
.new_blob_cache(config.config_v2(), &config.blob_info, "/")
504504
.inspect_err(|_e| {
505505
warn!(
506506
"blob_cache: failed to create cache object for blob {}",

service/src/fs_cache.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ impl FsCacheHandler {
573573
let mut blob_info = config.blob_info().deref().clone();
574574
blob_info.set_fscache_file(Some(file));
575575
let blob_ref = Arc::new(blob_info);
576-
BLOB_FACTORY.new_blob_cache(config.config_v2(), &blob_ref)
576+
BLOB_FACTORY.new_blob_cache(config.config_v2(), &blob_ref, "/")
577577
}
578578

579579
fn fill_bootstrap_cache(bootstrap: Arc<FsCacheBootstrap>) -> Result<u64> {

service/src/fs_service.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ pub trait FsService: Send + Sync {
146146
let rafs_cfg = ConfigV2::from_str(&cmd.config).map_err(RafsError::LoadConfig)?;
147147
let rafs_cfg = Arc::new(rafs_cfg);
148148

149-
rafs.update(&mut bootstrap, &rafs_cfg)
149+
rafs.update(&mut bootstrap, &rafs_cfg, &cmd.mountpoint)
150150
.map_err(|e| match e {
151151
RafsError::Unsupported => Error::Unsupported,
152152
e => Error::Rafs(e),

0 commit comments

Comments
 (0)