Skip to content

Commit e969948

Browse files
Fricounetimeoer
andauthored
api: support hot reload of registry authentication (#15)
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> Co-authored-by: imeoer <yansong.ys@antgroup.com>
1 parent 3a72cdb commit e969948

21 files changed

Lines changed: 941 additions & 67 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

@@ -156,6 +156,7 @@ lazy_static! {
156156
r.routes.insert(endpoint_v1!("/metrics/files"), Box::new(MetricsFsFilesHandler{}));
157157
r.routes.insert(endpoint_v1!("/metrics/inflight"), Box::new(MetricsFsInflightHandler{}));
158158
r.routes.insert(endpoint_v1!("/metrics/pattern"), Box::new(MetricsFsAccessPatternHandler{}));
159+
r.routes.insert(endpoint_v1!("/config"), Box::new(ConfigHandler{}));
159160

160161
// Nydus API, v2
161162
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)