From ac19cc32e22326a119a234a9a400c32b96fa36f1 Mon Sep 17 00:00:00 2001 From: Felicity Xu Date: Mon, 8 Jun 2026 22:41:10 +0000 Subject: [PATCH 1/2] feat: implement API to set NTP servers Signed-off-by: Felicity Xu --- src/ami.rs | 7 +++++++ src/dell.rs | 7 +++++++ src/hpe.rs | 7 +++++++ src/lenovo.rs | 7 +++++++ src/lib.rs | 6 ++++++ src/liteon_powershelf.rs | 7 +++++++ src/nvidia_dpu.rs | 7 +++++++ src/nvidia_gbswitch.rs | 7 +++++++ src/nvidia_gbx00.rs | 7 +++++++ src/nvidia_gh200.rs | 7 +++++++ src/nvidia_viking.rs | 7 +++++++ src/standard.rs | 17 +++++++++++++++++ src/supermicro.rs | 7 +++++++ 13 files changed, 100 insertions(+) diff --git a/src/ami.rs b/src/ami.rs index e57f6409a..a4134f11c 100644 --- a/src/ami.rs +++ b/src/ami.rs @@ -1166,6 +1166,13 @@ impl Redfish for Bmc { fn set_utc_timezone<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { Box::pin(async move { self.s.set_utc_timezone().await }) } + + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { + Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + } } impl Bmc { diff --git a/src/dell.rs b/src/dell.rs index cbb47a155..0073f564a 100644 --- a/src/dell.rs +++ b/src/dell.rs @@ -1388,6 +1388,13 @@ impl Redfish for Bmc { Ok(()) }) } + + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { + Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + } } impl Bmc { diff --git a/src/hpe.rs b/src/hpe.rs index 8311fd019..e6124e87e 100644 --- a/src/hpe.rs +++ b/src/hpe.rs @@ -1197,6 +1197,13 @@ impl Redfish for Bmc { fn set_utc_timezone<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { Box::pin(async move { self.s.set_utc_timezone().await }) } + + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { + Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + } } impl Bmc { diff --git a/src/lenovo.rs b/src/lenovo.rs index 98a112dec..c90eb6fd0 100644 --- a/src/lenovo.rs +++ b/src/lenovo.rs @@ -1230,6 +1230,13 @@ impl Redfish for Bmc { fn set_utc_timezone<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { Box::pin(async move { self.s.set_utc_timezone().await }) } + + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { + Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + } } impl Bmc { diff --git a/src/lib.rs b/src/lib.rs index c20c26272..241b2e29e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -663,6 +663,12 @@ pub trait Redfish: Send + Sync + 'static { // Sets the timezone to UTC // Only applicable to Dells fn set_utc_timezone<'a>(&'a self) -> RedfishFuture<'a, Result<(), RedfishError>>; + + // Sets the NTP servers + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> RedfishFuture<'a, Result<(), RedfishError>>; } #[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)] diff --git a/src/liteon_powershelf.rs b/src/liteon_powershelf.rs index ae9d8bd05..5f7135e79 100644 --- a/src/liteon_powershelf.rs +++ b/src/liteon_powershelf.rs @@ -921,6 +921,13 @@ impl Redfish for Bmc { fn set_utc_timezone<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { Box::pin(async move { self.s.set_utc_timezone().await }) } + + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { + Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + } } impl Bmc { diff --git a/src/nvidia_dpu.rs b/src/nvidia_dpu.rs index 5b9865081..5918c4a08 100644 --- a/src/nvidia_dpu.rs +++ b/src/nvidia_dpu.rs @@ -1101,6 +1101,13 @@ impl Redfish for Bmc { fn set_utc_timezone<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { Box::pin(async move { self.s.set_utc_timezone().await }) } + + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { + Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + } } impl Bmc { diff --git a/src/nvidia_gbswitch.rs b/src/nvidia_gbswitch.rs index 867f7e2f7..a2eb9a41a 100644 --- a/src/nvidia_gbswitch.rs +++ b/src/nvidia_gbswitch.rs @@ -1075,6 +1075,13 @@ impl Redfish for Bmc { fn set_utc_timezone<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { Box::pin(async move { self.s.set_utc_timezone().await }) } + + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { + Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + } } impl Bmc { diff --git a/src/nvidia_gbx00.rs b/src/nvidia_gbx00.rs index 77faafc29..4703cbd74 100644 --- a/src/nvidia_gbx00.rs +++ b/src/nvidia_gbx00.rs @@ -1329,6 +1329,13 @@ impl Redfish for Bmc { fn set_utc_timezone<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { Box::pin(async move { self.s.set_utc_timezone().await }) } + + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { + Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + } } impl Bmc { diff --git a/src/nvidia_gh200.rs b/src/nvidia_gh200.rs index c3c77ab2e..a6d2d2a94 100644 --- a/src/nvidia_gh200.rs +++ b/src/nvidia_gh200.rs @@ -1023,6 +1023,13 @@ impl Redfish for Bmc { fn set_utc_timezone<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { Box::pin(async move { self.s.set_utc_timezone().await }) } + + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { + Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + } } impl Bmc { diff --git a/src/nvidia_viking.rs b/src/nvidia_viking.rs index 41153e7b8..aecca95c9 100644 --- a/src/nvidia_viking.rs +++ b/src/nvidia_viking.rs @@ -1268,6 +1268,13 @@ impl Redfish for Bmc { fn set_utc_timezone<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { Box::pin(async move { self.s.set_utc_timezone().await }) } + + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { + Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + } } impl Bmc { diff --git a/src/standard.rs b/src/standard.rs index fd4a2436a..27ed9bd7a 100644 --- a/src/standard.rs +++ b/src/standard.rs @@ -1230,6 +1230,13 @@ impl Redfish for RedfishStandard { Ok(()) }) } + + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { + Box::pin(async move { self.set_manager_ntp_servers(servers).await }) + } } impl RedfishStandard { @@ -1624,6 +1631,16 @@ impl RedfishStandard { Ok(body) } + pub async fn set_manager_ntp_servers(&self, servers: &[String]) -> Result<(), RedfishError> { + if servers.is_empty() { + return Ok(()); + } + + let url = format!("Managers/{}/NetworkProtocol", self.manager_id(),); + let ntp_servers = HashMap::from([("NTP", json!({ "NTPServers": servers }))]); + self.client.patch(&url, ntp_servers).await.map(|_resp| ()) + } + pub async fn reset_manager( &self, reset_type: ManagerResetType, diff --git a/src/supermicro.rs b/src/supermicro.rs index 87cda0d2b..cbfd091a2 100644 --- a/src/supermicro.rs +++ b/src/supermicro.rs @@ -1166,6 +1166,13 @@ impl Redfish for Bmc { fn set_utc_timezone<'a>(&'a self) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { Box::pin(async move { self.s.set_utc_timezone().await }) } + + fn set_ntp_servers<'a>( + &'a self, + servers: &'a [String], + ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { + Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + } } impl Bmc { From f4d4e027b5f020413a745431019b801ba74b5d24 Mon Sep 17 00:00:00 2001 From: Felicity Xu Date: Wed, 10 Jun 2026 07:12:55 +0000 Subject: [PATCH 2/2] Add vendor-specific handling Signed-off-by: Felicity Xu --- src/dell.rs | 40 +++++++++++++++++++++++++++++++++++++++- src/hpe.rs | 16 +++++++++++++++- src/standard.rs | 9 ++++++++- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/dell.rs b/src/dell.rs index 0073f564a..6bfcfc86f 100644 --- a/src/dell.rs +++ b/src/dell.rs @@ -1393,7 +1393,45 @@ impl Redfish for Bmc { &'a self, servers: &'a [String], ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { - Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + Box::pin(async move { + if servers.is_empty() { + return Ok(()); + } + + let mut attrs = HashMap::new(); + attrs.insert("NTPConfigGroup.1.NTPEnable", "Enabled"); + if let Some(s1) = servers.first() { + attrs.insert("NTPConfigGroup.1.NTP1", s1.as_str()); + } + if let Some(s2) = servers.get(1) { + attrs.insert("NTPConfigGroup.1.NTP2", s2.as_str()); + } + if let Some(s3) = servers.get(2) { + attrs.insert("NTPConfigGroup.1.NTP3", s3.as_str()); + } + + // Try standard path first + let body = HashMap::from([("Attributes", attrs)]); + let manager_id = self.s.manager_id(); + let standard_url = format!("Managers/{manager_id}/Attributes"); + match self.s.client.patch(&standard_url, &body).await { + Ok(_) => return Ok(()), + Err(RedfishError::HTTPErrorCode { + status_code: StatusCode::NOT_FOUND, + .. + }) => { + tracing::info!( + "Managers/Attributes not found, using OEM DellAttributes path for NTP server config" + ); + } + Err(e) => return Err(e), + } + + // Fallback to OEM DellAttributes path + let oem_url = format!("Managers/{manager_id}/Oem/Dell/DellAttributes/{manager_id}"); + self.s.client.patch(&oem_url, body).await?; + Ok(()) + }) } } diff --git a/src/hpe.rs b/src/hpe.rs index e6124e87e..b1a3dbefb 100644 --- a/src/hpe.rs +++ b/src/hpe.rs @@ -1202,7 +1202,21 @@ impl Redfish for Bmc { &'a self, servers: &'a [String], ) -> crate::RedfishFuture<'a, Result<(), RedfishError>> { - Box::pin(async move { self.s.set_manager_ntp_servers(servers).await }) + Box::pin(async move { + if servers.is_empty() { + return Ok(()); + } + + // iLO supports at most 2 static NTP servers; extra entries are ignored. + let static_ntp_servers = vec![ + servers.first().cloned().unwrap_or_default(), + servers.get(1).cloned().unwrap_or_default(), + ]; + + let url = format!("Managers/{}/DateTime", self.s.manager_id()); + let body = HashMap::from([("StaticNTPServers", static_ntp_servers)]); + self.s.client.patch(&url, body).await.map(|_resp| ()) + }) } } diff --git a/src/standard.rs b/src/standard.rs index 27ed9bd7a..2ef2d3d30 100644 --- a/src/standard.rs +++ b/src/standard.rs @@ -1631,13 +1631,20 @@ impl RedfishStandard { Ok(body) } + /// Set NTP servers via the standard ManagerNetworkProtocol resource. pub async fn set_manager_ntp_servers(&self, servers: &[String]) -> Result<(), RedfishError> { if servers.is_empty() { return Ok(()); } let url = format!("Managers/{}/NetworkProtocol", self.manager_id(),); - let ntp_servers = HashMap::from([("NTP", json!({ "NTPServers": servers }))]); + let ntp_servers = HashMap::from([( + "NTP", + json!({ + "NTPServers": servers, + "ProtocolEnabled": true, + }), + )]); self.client.patch(&url, ntp_servers).await.map(|_resp| ()) }