diff --git a/Cargo.lock b/Cargo.lock index 6802648..b1f5e8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -851,8 +851,25 @@ dependencies = [ name = "libonm" version = "0.1.0" dependencies = [ + "async-trait", + "base64", "bindgen", + "bytes", "cc", + "clap", + "http", + "libudev", + "numeric_cast", + "reqwest", + "scopeguard", + "serde", + "serde_json", + "thiserror", + "tokio", + "toml", + "tracing", + "tracing-subscriber", + "url", ] [[package]] @@ -1003,6 +1020,12 @@ dependencies = [ "libc", ] +[[package]] +name = "numeric_cast" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf70ee2d9b1737d1836c20d9f8f96ec3901b2bf92128439db13237ddce9173a5" + [[package]] name = "object" version = "0.32.2" diff --git a/hcactl/src/list.rs b/hcactl/src/list.rs index 0c03192..85a0eb1 100644 --- a/hcactl/src/list.rs +++ b/hcactl/src/list.rs @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -use ::libhca; +use libonm::hca; pub fn run() -> Result<(), color_eyre::Report> { - let hcas = libhca::list_pci_devices()?; + let hcas = hca::list_pci_devices()?; for hca in hcas { println!("----------------------------------------------"); diff --git a/libonm/Cargo.toml b/libonm/Cargo.toml index c374286..503444c 100644 --- a/libonm/Cargo.toml +++ b/libonm/Cargo.toml @@ -4,7 +4,28 @@ version = "0.1.0" edition = "2021" [dependencies] +tracing = { workspace = true } +tracing-subscriber = { workspace = true } + +clap = { workspace = true } +serde = { workspace = true } +toml = { workspace = true } +tokio = { workspace = true } +async-trait = { workspace = true } +thiserror = { workspace = true } +serde_json = { workspace = true } + +reqwest = { workspace = true } +http = { workspace = true } +bytes = { workspace = true } +base64 = { workspace = true } +url = { workspace = true } + +numeric_cast = "0.2" +libudev = "0.3" +scopeguard = "1.2" + [build-dependencies] bindgen = "0.70" -cc = "1.0" \ No newline at end of file +cc = "1.0" diff --git a/libonm/src/hca/Cargo.toml b/libonm/src/hca/Cargo.toml deleted file mode 100644 index 59b20fb..0000000 --- a/libonm/src/hca/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "libhca" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -numeric_cast = "0.2" -libudev = "0.3" -scopeguard = "1.2" - -[build-dependencies] -bindgen = "0.69" -cc = "1.0" diff --git a/libonm/src/lib.rs b/libonm/src/lib.rs index bd62a45..055ff57 100644 --- a/libonm/src/lib.rs +++ b/libonm/src/lib.rs @@ -1,3 +1,5 @@ pub mod hca; +pub mod sm; pub mod xpu; -pub mod sm; \ No newline at end of file + +mod rest; diff --git a/libonm/src/rest.rs b/libonm/src/rest.rs new file mode 100644 index 0000000..9cd12bd --- /dev/null +++ b/libonm/src/rest.rs @@ -0,0 +1,182 @@ +use bytes::Bytes; +use http::Method; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use thiserror::Error; +use url::Url; + +use reqwest::{header::HeaderValue, header::ACCEPT, header::CONTENT_TYPE}; + +pub struct RestClient { + address: String, + user: String, + password: String, +} + +pub struct RestConfig { + pub address: String, + pub username: String, + pub password: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct EmptyResponse {} + +#[derive(Error, Debug)] +pub enum RestError { + #[error("{0}")] + Internal(String), + #[error("{0}")] + Json(String), + #[error("{0}")] + Http(String), + #[error("'{0}' not found")] + NotFound(String), + #[error("failed to auth '{0}'")] + AuthFailure(String), + #[error("invalid configuration '{0}'")] + InvalidConfig(String), +} + +impl From for RestError { + fn from(value: reqwest::Error) -> Self { + tracing::debug!("{:?}", value); + RestError::Http(value.to_string()) + } +} + +impl From for RestError { + fn from(value: serde_json::Error) -> Self { + tracing::debug!("{:?}", value); + RestError::Json(value.to_string()) + } +} + +impl From for RestError { + fn from(value: std::io::Error) -> Self { + RestError::Http(value.to_string()) + } +} + +impl RestClient { + pub fn new(config: &RestConfig) -> Result { + let url = Url::parse(&config.address) + .map_err(|_| RestError::InvalidConfig("invalid url".to_string()))?; + let host = url + .host_str() + .ok_or(RestError::InvalidConfig("invalid host".to_string()))?; + let port = url.port().unwrap_or(443); + let address = format!("{}:{}", host, port); + + Ok(RestClient { + address, + user: config.username.clone(), + password: config.password.clone(), + }) + } + + pub async fn get<'a, T: DeserializeOwned>(&self, path: &str) -> Result { + let resp = self.execute_request(Method::GET, path, None).await?; + let data = serde_json::from_str(&resp) + .map_err(|_| RestError::InvalidConfig("invalid response".to_string()))?; + + Ok(data) + } + + pub async fn list<'a, T: DeserializeOwned>(&self, path: &str) -> Result, RestError> { + let resp = self.execute_request(Method::GET, path, None).await?; + let data = serde_json::from_str(&resp) + .map_err(|_| RestError::InvalidConfig("invalid response".to_string()))?; + + Ok(data) + } + + pub async fn put<'a, S: Serialize, T: DeserializeOwned>( + &self, + path: &str, + o: &S, + ) -> Result { + let input = serde_json::to_string(o) + .map_err(|_| RestError::InvalidConfig("invalid input".to_string()))?; + let resp = self.execute_request(Method::PUT, path, Some(input)).await?; + + let data = serde_json::from_str(&resp) + .map_err(|_| RestError::InvalidConfig("invalid response".to_string()))?; + + Ok(data) + } + + pub async fn post<'a, S: Serialize, T: DeserializeOwned>( + &self, + path: &str, + o: &S, + ) -> Result { + let input = serde_json::to_string(o) + .map_err(|_| RestError::InvalidConfig("invalid input".to_string()))?; + let resp = self + .execute_request(Method::POST, path, Some(input)) + .await?; + + let data = serde_json::from_str(&resp) + .map_err(|_| RestError::InvalidConfig("invalid response".to_string()))?; + + Ok(data) + } + + pub async fn delete<'a, T: DeserializeOwned>(&self, path: &str) -> Result { + let resp = self.execute_request(Method::DELETE, path, None).await?; + + let data = serde_json::from_str(&resp) + .map_err(|_| RestError::InvalidConfig("invalid response".to_string()))?; + + Ok(data) + } + + pub async fn patch<'a, S: Serialize, T: DeserializeOwned>( + &self, + path: &str, + o: &S, + ) -> Result { + let input = serde_json::to_string(o) + .map_err(|_| RestError::InvalidConfig("invalid input".to_string()))?; + let resp = self + .execute_request(Method::PATCH, path, Some(input)) + .await?; + + let data = serde_json::from_str(&resp) + .map_err(|_| RestError::InvalidConfig("invalid response".to_string()))?; + + Ok(data) + } + + async fn execute_request( + &self, + method: Method, + path: &str, + data: Option, + ) -> Result { + let schema = "https"; + let url = format!("{}://{}/{}", schema, self.address, path.trim_matches('/')); + + let body = Bytes::from(data.clone().unwrap_or(String::new())); + tracing::debug!( + "Method: {method}, URL: {url}, Auth: <{0}/{1}>, Body: <{2}>", + self.user, + self.password, + data.unwrap_or(String::new()) + ); + + let client = reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(true) + .build()?; + let req = client + .request(method, url) + .header(ACCEPT, HeaderValue::from_static("application/json")) + .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) + .body(body) + .basic_auth(&self.user, Some(self.password.clone())) + .build()?; + let resp = client.execute(req).await?; + + Ok(resp.text().await?) + } +} diff --git a/libonm/src/sm/mod.rs b/libonm/src/sm/mod.rs index 6a1c3e3..025e1fc 100644 --- a/libonm/src/sm/mod.rs +++ b/libonm/src/sm/mod.rs @@ -1,14 +1,12 @@ use std::collections::HashMap; -use base64::prelude::*; use serde::{Deserialize, Serialize}; use thiserror::Error; use url::Url; -use self::rest::{RestClient, RestClientConfig, RestError, RestScheme}; use self::types::{Configuration, PhysicalPort, Port}; +use crate::rest::{RestClient, RestConfig, RestError}; -mod rest; mod types; pub use types::PortType; @@ -182,10 +180,10 @@ pub enum UFMError { impl From for UFMError { fn from(e: RestError) -> Self { match e { - RestError::Unknown(msg) => UFMError::Unknown(msg), RestError::NotFound(msg) => UFMError::NotFound(msg), RestError::AuthFailure(msg) => UFMError::InvalidConfig(msg), RestError::InvalidConfig(msg) => UFMError::InvalidConfig(msg), + _ => UFMError::Unknown(String::from("Unknown Rest Error")), } } } @@ -212,41 +210,19 @@ pub fn connect(conf: UFMConfig) -> Result { .host_str() .ok_or(UFMError::InvalidConfig("invalid UFM host".to_string()))?; - let (base_path, auth_info) = match &conf.token { - None if conf.cert.is_some() => { - let auth_cert = conf.cert.unwrap().clone(); - ( - "/ufmRest".to_string(), - format!( - "{}\n{}\n{}", - auth_cert.ca_crt, auth_cert.tls_key, auth_cert.tls_crt - ), - ) - } - None => { - let password = conf - .password - .clone() - .ok_or(UFMError::InvalidConfig("password is empty".to_string()))?; - let username = conf - .username - .clone() - .ok_or(UFMError::InvalidConfig("username is empty".to_string()))?; - - ( - "/ufmRest".to_string(), - BASE64_STANDARD.encode(format!("{}:{}", username, password)), - ) - } - Some(t) => ("/ufmRestV3".to_string(), t.to_string()), - }; + let password = conf + .password + .clone() + .ok_or(UFMError::InvalidConfig("password is empty".to_string()))?; + let username = conf + .username + .clone() + .ok_or(UFMError::InvalidConfig("username is empty".to_string()))?; - let c = RestClient::new(&RestClientConfig { + let c = RestClient::new(&RestConfig { address: address.to_string(), - port: addr.port(), - auth_info, - base_path, - scheme: RestScheme::from(addr.scheme().to_string()), + password, + username, })?; Ok(Ufm { client: c }) @@ -266,15 +242,14 @@ impl Ufm { .qos .ok_or(UFMError::InvalidConfig("no partition qos".to_string()))?; - let data = serde_json::to_string(&PKeyQoS { + let qos = PKeyQoS { pkey: p.pkey.to_string(), mtu_limit: qos.mtu_limit, rate_limit: qos.rate_limit, service_level: qos.service_level, - }) - .map_err(|_| UFMError::InvalidConfig("invalid partition".to_string()))?; + }; - self.client.put(&path, data).await?; + let _ = self.client.put(&path, &qos).await?; Ok(()) } @@ -300,10 +275,7 @@ impl Ufm { guids, }; - let data = serde_json::to_string(&pkey) - .map_err(|_| UFMError::InvalidConfig("invalid partition".to_string()))?; - - self.client.post(&path, data).await?; + let _ = self.client.post(&path, &pkey).await?; Ok(()) } @@ -326,10 +298,7 @@ impl Ufm { guids, }; - let data = serde_json::to_string(&pkey) - .map_err(|_| UFMError::InvalidConfig("invalid partition".to_string()))?; - - self.client.post(&path, data).await?; + self.client.post(&path, &pkey).await?; Ok(()) } @@ -364,7 +333,7 @@ impl Ufm { } let path = String::from("/resources/pkeys?qos_conf=true"); - let pkey_qos: HashMap = self.client.list(&path).await?; + let pkey_qos: Vec<(String, Pkey)> = self.client.list(&path).await?; let mut parts = Vec::new(); diff --git a/libonm/src/sm/rest.rs b/libonm/src/sm/rest.rs deleted file mode 100644 index 8fd65da..0000000 --- a/libonm/src/sm/rest.rs +++ /dev/null @@ -1,373 +0,0 @@ -use std::fmt; -use std::fmt::{Display, Formatter}; -use std::time::Duration; - -use hyper::client::HttpConnector; -use hyper::header::{AUTHORIZATION, CONTENT_TYPE, USER_AGENT}; -use hyper::http::StatusCode; -use hyper::{Body, Client, Method, Uri}; -use hyper_rustls::HttpsConnector; -use hyper_timeout::TimeoutConnector; -use std::time::SystemTime; -use thiserror::Error; -use tokio_rustls::rustls; -use tokio_rustls::rustls::client::{ServerCertVerified, ServerCertVerifier}; -use tokio_rustls::rustls::{Certificate, ClientConfig, PrivateKey, RootCertStore, ServerName}; - -use crate::UFMCert; - -struct NoCertificateVerification; - -impl ServerCertVerifier for NoCertificateVerification { - fn verify_server_cert( - &self, - _end_entity: &rustls::Certificate, - _intermediates: &[rustls::Certificate], - _server_name: &ServerName, - _scts: &mut dyn Iterator, - _ocsp_response: &[u8], - _now: SystemTime, - ) -> Result { - Ok(ServerCertVerified::assertion()) - } -} - -const REST_TIME_OUT: Duration = Duration::from_secs(10); - -#[derive(Error, Debug)] -pub enum RestError { - #[error("{0}")] - Unknown(String), - #[error("'{0}' not found")] - NotFound(String), - #[error("failed to auth '{0}'")] - AuthFailure(String), - #[error("invalid configuration '{0}'")] - InvalidConfig(String), -} - -impl From for RestError { - fn from(value: hyper::Error) -> Self { - if value.is_user() { - return RestError::AuthFailure(value.message().to_string()); - } - - RestError::Unknown(value.message().to_string()) - } -} - -#[derive(Clone, Debug)] -pub enum RestScheme { - Http, - Https, -} - -impl From for RestScheme { - fn from(value: String) -> Self { - match value.to_uppercase().as_str() { - "HTTP" => RestScheme::Http, - "HTTPS" => RestScheme::Https, - _ => RestScheme::Http, - } - } -} - -impl Display for RestScheme { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - RestScheme::Http => write!(f, "http"), - RestScheme::Https => write!(f, "https"), - } - } -} - -pub struct RestClientConfig { - pub address: String, - pub port: Option, - pub scheme: RestScheme, - pub auth_info: String, - pub base_path: String, -} - -pub struct RestClient { - base_url: String, - auth_info: String, - scheme: RestScheme, - http_client: hyper::Client>, - https_client: hyper::Client>>, -} - -impl RestClient { - pub fn new(conf: &RestClientConfig) -> Result { - let mut auth_info = conf.auth_info.clone().trim().to_string(); - let mut auto_cert: Option = None; - - if auth_info.chars().filter(|c| *c == '\n').count() == 2 { - let mut v = auth_info.split('\n'); - auto_cert = Some(UFMCert { - ca_crt: v.next().unwrap_or("").to_string(), - tls_key: v.next().unwrap_or("").to_string(), - tls_crt: v.next().unwrap_or("").to_string(), - }); - auth_info = "".to_string(); - } else { - auth_info = format!("Basic {}", conf.auth_info.clone().trim()); - } - - let base_url = match &conf.port { - None => format!( - "{}://{}/{}", - conf.scheme, - conf.address, - conf.base_path.trim_matches('/') - ), - Some(p) => format!( - "{}://{}:{}/{}", - conf.scheme, - conf.address, - p, - conf.base_path.trim_matches('/') - ), - }; - - let _ = base_url - .parse::() - .map_err(|_| RestError::InvalidConfig("invalid rest address".to_string()))?; - - let mut http_connector = TimeoutConnector::new(HttpConnector::new()); - http_connector.set_connect_timeout(Some(REST_TIME_OUT)); - http_connector.set_read_timeout(Some(REST_TIME_OUT)); - http_connector.set_write_timeout(Some(REST_TIME_OUT)); - - let config = if auto_cert.is_some() { - // Get CA root - let mut roots = RootCertStore::empty(); - let fd = match std::fs::File::open(auto_cert.clone().unwrap().ca_crt) { - Ok(fd) => fd, - Err(_) => { - return Err(RestError::NotFound(format!( - "Root CA file not found at '{}'", - auto_cert.clone().unwrap().ca_crt - ))); - } - }; - let mut buf = std::io::BufReader::new(&fd); - match rustls_pemfile::certs(&mut buf) { - Ok(certs) => roots.add_parsable_certificates(&certs), - Err(_) => { - return Err(RestError::NotFound(format!( - "Root CA file not found at '{}'", - auto_cert.clone().unwrap().tls_crt - ))); - } - }; - - // Get client certificate - let certs = { - let fd = match std::fs::File::open(auto_cert.clone().unwrap().tls_crt) { - Ok(fd) => fd, - Err(_) => { - return Err(RestError::NotFound(format!( - "Client Cert file not found at '{}'", - auto_cert.clone().unwrap().tls_crt - ))); - } - }; - let mut buf = std::io::BufReader::new(&fd); - match rustls_pemfile::certs(&mut buf) { - Ok(certs) => certs.into_iter().map(Certificate).collect::>(), - Err(_) => { - return Err(RestError::NotFound(format!( - "Client Cert file not found at '{}'", - auto_cert.clone().unwrap().tls_crt - ))); - } - } - }; - - // Get client private key - let key = { - let fd = match std::fs::File::open(auto_cert.clone().unwrap().tls_key) { - Ok(fd) => fd, - Err(_) => { - return Err(RestError::NotFound(format!( - "Client Private Key file not found at '{}'", - auto_cert.clone().unwrap().tls_key - ))); - } - }; - let mut buf = std::io::BufReader::new(&fd); - use rustls_pemfile::Item; - match rustls_pemfile::read_one(&mut buf) { - Ok(Some(item)) => match item { - Item::RSAKey(rsa_key) => Some(PrivateKey(rsa_key)), - Item::PKCS8Key(pkcs8_key) => Some(PrivateKey(pkcs8_key)), - Item::ECKey(ec_key) => Some(PrivateKey(ec_key)), - Item::X509Certificate(_) => { - return Err(RestError::NotFound(format!( - "Expected Client Private Key but certificate is found '{}'", - auto_cert.clone().unwrap().tls_key - ))); - } - Item::Crl(_) => { - return Err(RestError::NotFound(format!("Expected Client Private Key but certificate revocation list is found '{}'", auto_cert.clone().unwrap().tls_key))); - } - _ => { - return Err(RestError::NotFound(format!( - "Client Private Key is corrupted '{}'", - auto_cert.clone().unwrap().tls_key - ))); - } - }, - _ => { - return Err(RestError::NotFound(format!( - "Client Private Key file not found at '{}'", - auto_cert.clone().unwrap().tls_key - ))); - } - } - }; - - let build_no_client_auth_config = || { - ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(roots.clone()) - .with_no_client_auth() - }; - - if !certs.is_empty() && key.is_some() { - if let Ok(config) = ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(roots.clone()) - .with_client_auth_cert(certs, key.unwrap()) - { - // Use TLS flow with client authentication - config - } else { - // Client creation failure - build_no_client_auth_config() - } - } else { - // Unable to use client cert/key pair - build_no_client_auth_config() - } - } else { - // No TLS flow - ClientConfig::builder() - .with_safe_defaults() - .with_custom_certificate_verifier(std::sync::Arc::new(NoCertificateVerification)) - .with_no_client_auth() - }; - - let mut https_connector = TimeoutConnector::new( - hyper_rustls::HttpsConnectorBuilder::new() - .with_tls_config(config) - .https_or_http() - .enable_http1() - .enable_http2() - .build(), - ); - https_connector.set_connect_timeout(Some(REST_TIME_OUT)); - https_connector.set_read_timeout(Some(REST_TIME_OUT)); - https_connector.set_write_timeout(Some(REST_TIME_OUT)); - - Ok(Self { - base_url, - auth_info, - scheme: conf.scheme.clone(), - // TODO(k82cn): Add timout for the clients. - http_client: Client::builder().build::<_, hyper::Body>(http_connector), - https_client: Client::builder().build::<_, hyper::Body>(https_connector), - }) - } - - pub async fn get<'a, T: serde::de::DeserializeOwned>( - &'a self, - path: &'a str, - ) -> Result { - let resp = self.execute_request(Method::GET, path, None).await?; - if resp.eq("{}") { - return Err(RestError::NotFound("not found".to_string())); - } - - let data = serde_json::from_str(&resp) - .map_err(|_| RestError::InvalidConfig("invalid response".to_string()))?; - - Ok(data) - } - - pub async fn list<'a, T: serde::de::DeserializeOwned>( - &'a self, - path: &'a str, - ) -> Result { - let resp = self.execute_request(Method::GET, path, None).await?; - let data = serde_json::from_str(&resp) - .map_err(|_| RestError::InvalidConfig("invalid response".to_string()))?; - - Ok(data) - } - - pub async fn post(&self, path: &str, data: String) -> Result<(), RestError> { - self.execute_request(Method::POST, path, Some(data)).await?; - - Ok(()) - } - - pub async fn put(&self, path: &str, data: String) -> Result<(), RestError> { - self.execute_request(Method::PUT, path, Some(data)).await?; - - Ok(()) - } - - pub async fn delete(&self, path: &str) -> Result<(), RestError> { - self.execute_request(Method::DELETE, path, None).await?; - - Ok(()) - } - - async fn execute_request( - &self, - method: Method, - path: &str, - data: Option, - ) -> Result { - let url = format!("{}/{}", self.base_url, path.trim_matches('/')); - let uri = url - .parse::() - .map_err(|_| RestError::InvalidConfig("invalid path".to_string()))?; - - let body = data.unwrap_or_default(); - log::debug!("Method: {method}, URL: {url}, Body: {body}"); - - let req = hyper::Request::builder() - .method(method) - .uri(uri) - .header(USER_AGENT, env!("CARGO_PKG_NAME")) - .header(CONTENT_TYPE, "application/json") - .header(AUTHORIZATION, self.auth_info.to_string()) - .body(Body::from(body)) - .map_err(|_| RestError::InvalidConfig("invalid rest request".to_string()))?; - - let body = match &self.scheme { - RestScheme::Http => self - .http_client - .request(req) - .await - .map_err(|e| RestError::Unknown(format!("rest request failure: {:?}", e)))?, - RestScheme::Https => self - .https_client - .request(req) - .await - .map_err(|e| RestError::Unknown(format!("rest request failure: {:?}", e)))?, - }; - - let status = body.status(); - let chunk = hyper::body::to_bytes(body.into_body()).await?; - let data = String::from_utf8(chunk.to_vec()).unwrap(); - - match status { - StatusCode::OK => Ok(data), - _ => Err(RestError::Unknown(data)), - } - } -} diff --git a/libonm/src/xpu/mod.rs b/libonm/src/xpu/mod.rs index e69de29..5aec112 100644 --- a/libonm/src/xpu/mod.rs +++ b/libonm/src/xpu/mod.rs @@ -0,0 +1,4 @@ +mod redfish; +mod types; + +pub use types::{BMCVersion, XPUError, XPUStatus, BMC, XPU}; diff --git a/libonm/src/xpu/redfish/bluefield.rs b/libonm/src/xpu/redfish/bluefield.rs index 0213e4f..0e5c7dc 100644 --- a/libonm/src/xpu/redfish/bluefield.rs +++ b/libonm/src/xpu/redfish/bluefield.rs @@ -1,13 +1,11 @@ -use crate::redfish::Redfish; -use crate::types::BMC; -use async_trait::async_trait; +use super::{BMCVersion, Redfish, RedfishError, BMC}; -use super::rest::{RestClient, RestConfig}; -use super::xpu::BMCVersion; -use super::RedfishError; +use crate::rest::{RestClient, RestConfig}; +use async_trait::async_trait; pub struct Bluefield { rest: RestClient, + bmc: BMC, } const DEFAULT_PASSWORD: &str = "0penBmc"; @@ -19,10 +17,9 @@ impl Redfish for Bluefield { async fn change_password(&self, passwd: String) -> Result<(), RedfishError> { let mut data = std::collections::HashMap::new(); data.insert("Password", passwd); - let data = serde_json::to_string(&data).unwrap(); self.rest - .patch("/redfish/v1/AccountService/Accounts/root", data) + .patch("/redfish/v1/AccountService/Accounts/root", &data) .await .map_err(RedfishError::from)?; @@ -36,7 +33,25 @@ impl Redfish for Bluefield { .await .map_err(RedfishError::from)?; - serde_json::from_str(resp.as_str()).map_err(|e| RedfishError::Json(e.to_string())) + Ok(resp) + } + + async fn discover(&self) -> Result<(), RedfishError> { + if self.bmc_version().await.is_ok() { + return Ok(()); + } + + // Try to change the default password. + let default_bmc = Bluefield::default_bmc(&self.bmc.username, &self.bmc.address); + let default_redfish = Box::new(Bluefield::new(&default_bmc)?); + default_redfish + .change_password(self.bmc.password.clone()) + .await?; + + // Retry BMC version by the password. + let _ = self.bmc_version().await?; + + Ok(()) } } @@ -44,22 +59,21 @@ impl Bluefield { pub fn new(bmc: &BMC) -> Result { let config = RestConfig { address: bmc.address.clone(), - password: bmc.password.clone().unwrap_or(DEFAULT_PASSWORD.to_string()), - username: bmc.username.clone().unwrap_or(DEFAULT_USER.to_string()), + password: bmc.password.clone(), + username: bmc.username.clone(), }; Ok(Bluefield { rest: RestClient::new(&config)?, + bmc: bmc.clone(), }) } - pub fn default_bmc(name: &str, addr: &str) -> BMC { + fn default_bmc(name: &str, addr: &str) -> BMC { BMC { - name: name.to_string(), address: addr.to_string(), - vendor: VENDOR.to_string(), - password: Some(DEFAULT_PASSWORD.to_string()), - username: Some(DEFAULT_USER.to_string()), + password: DEFAULT_PASSWORD.to_string(), + username: DEFAULT_USER.to_string(), } } } diff --git a/libonm/src/xpu/redfish/mod.rs b/libonm/src/xpu/redfish/mod.rs index ae11ffe..0deb1a2 100644 --- a/libonm/src/xpu/redfish/mod.rs +++ b/libonm/src/xpu/redfish/mod.rs @@ -1,18 +1,16 @@ -use crate::types::{Context, BMC}; +use crate::rest::RestError; use async_trait::async_trait; -use rest::RestError; -use serde::{Deserialize, Serialize}; -use std::{fmt, io, sync::Arc}; use thiserror::Error; -mod bluefield; -mod rest; -mod xpu; +use super::{BMCVersion, BMC}; + +use bluefield::Bluefield; -pub use xpu::{discover, BMCVersion, XPU}; +mod bluefield; #[async_trait] -trait Redfish { +pub trait Redfish { + async fn discover(&self) -> Result<(), RedfishError>; async fn change_password(&self, passwd: String) -> Result<(), RedfishError>; async fn bmc_version(&self) -> Result; } @@ -38,3 +36,7 @@ impl From for RedfishError { RedfishError::IOError(value.to_string()) } } + +pub fn build(bmc: &BMC) -> Result, RedfishError> { + Ok(Box::new(Bluefield::new(bmc)?)) +} diff --git a/libonm/src/xpu/redfish/rest.rs b/libonm/src/xpu/redfish/rest.rs deleted file mode 100644 index d7de969..0000000 --- a/libonm/src/xpu/redfish/rest.rs +++ /dev/null @@ -1,130 +0,0 @@ -use bytes::Bytes; -use http::Method; -use serde::de::DeserializeOwned; -use serde::Serialize; -use thiserror::Error; -use url::Url; - -use reqwest::{header::HeaderValue, header::ACCEPT, header::CONTENT_TYPE}; - -pub struct RestClient { - address: String, - user: String, - password: String, -} - -pub struct RestConfig { - pub address: String, - pub username: String, - pub password: String, -} - -#[derive(Error, Debug)] -pub enum RestError { - #[error("{0}")] - Internal(String), - #[error("{0}")] - Json(String), - #[error("{0}")] - Http(String), - #[error("'{0}' not found")] - NotFound(String), - #[error("failed to auth '{0}'")] - AuthFailure(String), - #[error("invalid configuration '{0}'")] - InvalidConfig(String), -} - -impl From for RestError { - fn from(value: reqwest::Error) -> Self { - tracing::debug!("{:?}", value); - RestError::Http(value.to_string()) - } -} - -impl From for RestError { - fn from(value: serde_json::Error) -> Self { - tracing::debug!("{:?}", value); - RestError::Json(value.to_string()) - } -} - -impl From for RestError { - fn from(value: std::io::Error) -> Self { - RestError::Http(value.to_string()) - } -} - -impl RestClient { - pub fn new(config: &RestConfig) -> Result { - let url = Url::parse(&config.address) - .map_err(|_| RestError::InvalidConfig("invalid BMC url".to_string()))?; - let host = url - .host_str() - .ok_or(RestError::InvalidConfig("invalid BMC host".to_string()))?; - let port = url.port().unwrap_or(443); - let address = format!("{}:{}", host, port); - - Ok(RestClient { - address, - user: config.username.clone(), - password: config.password.clone(), - }) - } - - pub async fn get(&self, path: &str) -> Result { - let body = self.execute_request(Method::GET, path, None).await?; - - Ok(body) - } - - pub async fn put(&self, path: &str, o: String) -> Result { - let body = self.execute_request(Method::PUT, path, Some(o)).await?; - - Ok(body) - } - - pub async fn delete(&self, path: &str) -> Result { - let body = self.execute_request(Method::DELETE, path, None).await?; - - Ok(body) - } - - pub async fn patch(&self, path: &str, o: String) -> Result { - let body = self.execute_request(Method::PATCH, path, Some(o)).await?; - - Ok(body) - } - - async fn execute_request( - &self, - method: Method, - path: &str, - data: Option, - ) -> Result { - let schema = "https"; - let url = format!("{}://{}/{}", schema, self.address, path.trim_matches('/')); - - let body = Bytes::from(data.clone().unwrap_or(String::new())); - tracing::debug!( - "Method: {method}, URL: {url}, Auth: <{0}/{1}>, Body: <{2}>", - self.user, - self.password, - data.unwrap_or(String::new()) - ); - - let client = reqwest::ClientBuilder::new() - .danger_accept_invalid_certs(true) - .build()?; - let req = client - .request(method, url) - .header(ACCEPT, HeaderValue::from_static("application/json")) - .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) - .body(body) - .basic_auth(&self.user, Some(self.password.clone())) - .build()?; - let resp = client.execute(req).await?; - - Ok(resp.text().await?) - } -} diff --git a/libonm/src/xpu/redfish/xpu.rs b/libonm/src/xpu/types.rs similarity index 58% rename from libonm/src/xpu/redfish/xpu.rs rename to libonm/src/xpu/types.rs index 6f15110..f96236e 100644 --- a/libonm/src/xpu/redfish/xpu.rs +++ b/libonm/src/xpu/types.rs @@ -1,9 +1,7 @@ -use crate::types::{Context, BMC}; -use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use std::{fmt, io, sync::Arc}; +use thiserror::Error; -use super::{bluefield::Bluefield, Redfish, RedfishError}; +use super::redfish::{self, Redfish, RedfishError}; #[derive(Clone, Debug, Serialize, Deserialize)] pub enum XPUStatus { @@ -12,6 +10,29 @@ pub enum XPUStatus { Unknown, } +#[derive(Error, Debug)] +pub enum XPUError { + #[error("{0}")] + Internal(String), + #[error("'{0}' not found")] + NotFound(String), + #[error("invalid configuration '{0}'")] + InvalidConfig(String), +} + +impl From for XPUError { + fn from(value: RedfishError) -> Self { + XPUError::Internal(value.to_string()) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BMC { + pub address: String, + pub username: String, + pub password: String, +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct BMCVersion { @@ -46,13 +67,13 @@ impl ToString for XPUStatus { } impl XPU { - pub async fn new(bmc: &BMC) -> Result { - let redfish: Box = Box::new(Bluefield::new(bmc)?); + pub async fn new(bmc: &BMC) -> Result { + let redfish = redfish::build(bmc)?; let bmc_ver = redfish.bmc_version().await?; let xpu = XPU { redfish, - vendor: bmc.vendor.clone(), + vendor: "-".to_string(), serial_number: "-".to_string(), firmware_version: "-".to_string(), bmc_version: bmc_ver.version, @@ -63,23 +84,3 @@ impl XPU { Ok(xpu) } } - -pub async fn discover(bmc: &BMC) -> Result<(), RedfishError> { - let redfish = Box::new(Bluefield::new(bmc)?); - - if redfish.bmc_version().await.is_ok() { - return Ok(()); - } - - // Try to change the default password. - let default_bmc = Bluefield::default_bmc(&bmc.name, &bmc.address); - let default_redfish = Box::new(Bluefield::new(&default_bmc)?); - default_redfish - .change_password(bmc.password.clone().unwrap()) - .await?; - - // Retry BMC version by the password. - let _ = redfish.bmc_version().await?; - - Ok(()) -} diff --git a/smctl/src/bind.rs b/smctl/src/bind.rs index 7c03934..8830ec2 100644 --- a/smctl/src/bind.rs +++ b/smctl/src/bind.rs @@ -1,7 +1,7 @@ -use libufm::{Partition, PartitionKey, PortConfig, PortMembership, UFMConfig, UFMError}; +use libonm::sm::{self, Partition, PartitionKey, PortConfig, PortMembership, UFMConfig, UFMError}; pub async fn run(conf: UFMConfig, pkey: &str, guids: &Vec) -> Result<(), UFMError> { - let ufm = libufm::connect(conf)?; + let ufm = sm::connect(conf)?; let mut pbs = vec![]; for g in guids { diff --git a/smctl/src/create.rs b/smctl/src/create.rs index 421e694..691ae5e 100644 --- a/smctl/src/create.rs +++ b/smctl/src/create.rs @@ -1,4 +1,4 @@ -use libufm::{Partition, PartitionKey, PortConfig, PortMembership, UFMConfig, UFMError}; +use libonm::sm::{self, Partition, PartitionKey, PortConfig, PortMembership, UFMConfig, UFMError}; pub struct CreateOptions { pub pkey: String, @@ -9,7 +9,7 @@ pub struct CreateOptions { } pub async fn run(conf: UFMConfig, opt: &CreateOptions) -> Result<(), UFMError> { - let ufm = libufm::connect(conf)?; + let ufm = sm::connect(conf)?; let mut pbs = vec![]; for g in &opt.guids { diff --git a/smctl/src/delete.rs b/smctl/src/delete.rs index 6fd43bc..e3a9c0f 100644 --- a/smctl/src/delete.rs +++ b/smctl/src/delete.rs @@ -1,7 +1,7 @@ -use libufm::{UFMConfig, UFMError}; +use libonm::sm::{self, UFMConfig, UFMError}; pub async fn run(conf: UFMConfig, pkey: &str) -> Result<(), UFMError> { - let ufm = libufm::connect(conf)?; + let ufm = sm::connect(conf)?; ufm.delete_partition(pkey).await?; Ok(()) diff --git a/smctl/src/info.rs b/smctl/src/info.rs index ed85024..19d287e 100644 --- a/smctl/src/info.rs +++ b/smctl/src/info.rs @@ -1,7 +1,7 @@ -use libufm::{UFMConfig, UFMError}; +use libonm::sm::{self, UFMConfig, UFMError}; pub async fn run(conf: UFMConfig) -> Result<(), UFMError> { - let ufm = libufm::connect(conf)?; + let ufm = sm::connect(conf)?; let config = ufm.get_configuration().await?; println!("subnet prefix : {}", config.subnet_prefix); diff --git a/smctl/src/list.rs b/smctl/src/list.rs index 192cdb0..55cae4d 100644 --- a/smctl/src/list.rs +++ b/smctl/src/list.rs @@ -1,7 +1,7 @@ -use libufm::{UFMConfig, UFMError}; +use libonm::sm::{self, UFMConfig, UFMError}; pub async fn run(conf: UFMConfig) -> Result<(), UFMError> { - let ufm = libufm::connect(conf)?; + let ufm = sm::connect(conf)?; let ps = ufm.list_partition().await?; println!( diff --git a/smctl/src/main.rs b/smctl/src/main.rs index b811b41..01bc234 100644 --- a/smctl/src/main.rs +++ b/smctl/src/main.rs @@ -1,6 +1,6 @@ use clap::{Parser, Subcommand}; -use libufm::{UFMCert, UFMConfig, UFMError}; +use libonm::sm::{UFMCert, UFMConfig, UFMError}; mod bind; mod create; diff --git a/smctl/src/unbind.rs b/smctl/src/unbind.rs index 2866e9f..702db62 100644 --- a/smctl/src/unbind.rs +++ b/smctl/src/unbind.rs @@ -1,7 +1,7 @@ -use libufm::{PartitionKey, UFMConfig, UFMError}; +use libonm::sm::{self, PartitionKey, UFMConfig, UFMError}; pub async fn run(conf: UFMConfig, pkey: &str, guids: &[String]) -> Result<(), UFMError> { - let ufm = libufm::connect(conf)?; + let ufm = sm::connect(conf)?; let p = PartitionKey::try_from(pkey.to_owned())?; diff --git a/smctl/src/update.rs b/smctl/src/update.rs index 4d2905c..6fc06d3 100644 --- a/smctl/src/update.rs +++ b/smctl/src/update.rs @@ -1,4 +1,4 @@ -use libufm::{Partition, PartitionKey, PartitionQoS, UFMConfig, UFMError}; +use libonm::sm::{self, Partition, PartitionKey, PartitionQoS, UFMConfig, UFMError}; pub struct UpdateOptions { pub pkey: String, @@ -10,7 +10,7 @@ pub struct UpdateOptions { } pub async fn run(conf: UFMConfig, opt: &UpdateOptions) -> Result<(), UFMError> { - let ufm = libufm::connect(conf)?; + let ufm = sm::connect(conf)?; let p = Partition { name: "".to_string(), diff --git a/smctl/src/version.rs b/smctl/src/version.rs index a6ee641..b94adef 100644 --- a/smctl/src/version.rs +++ b/smctl/src/version.rs @@ -1,7 +1,7 @@ -use libufm::{UFMConfig, UFMError}; +use libonm::sm::{self, UFMConfig, UFMError}; pub async fn run(conf: UFMConfig) -> Result<(), UFMError> { - let ufm = libufm::connect(conf)?; + let ufm = sm::connect(conf)?; let v = ufm.version().await?; println!("{}", v); diff --git a/smctl/src/view.rs b/smctl/src/view.rs index d98b6a7..dc3920e 100644 --- a/smctl/src/view.rs +++ b/smctl/src/view.rs @@ -1,7 +1,7 @@ -use libufm::{PortType, UFMConfig, UFMError}; +use libonm::sm::{self, PortType, UFMConfig, UFMError}; pub async fn run(conf: UFMConfig, pkey: &str) -> Result<(), UFMError> { - let ufm = libufm::connect(conf)?; + let ufm = sm::connect(conf)?; let p = ufm.get_partition(pkey).await?; let ps = ufm.list_port(p.pkey).await?; diff --git a/xpuctl/src/discover.rs b/xpuctl/src/discover.rs index c54582f..29adc92 100644 --- a/xpuctl/src/discover.rs +++ b/xpuctl/src/discover.rs @@ -1,11 +1,12 @@ -use libonm::redfish::{self, RedfishError}; +use libonm::xpu::{XPUError, BMC, XPU}; + use crate::types::Context; -pub async fn run(cxt: &Context) -> Result<(), RedfishError> { +pub async fn run(cxt: &Context) -> Result<(), XPUError> { println!("{:<20}{:<30}{:<50}", "Name", "BMC", "Status"); for bmc in cxt.bmc.iter() { - match redfish::discover(bmc).await { + match XPU::new(&BMC::from(bmc)).await { Ok(_) => println!("{:<20}{:<30}{:<50}", bmc.name, bmc.address, "Ok"), Err(e) => println!("{:<20}{:<30}{:<50}", bmc.name, bmc.address, e.to_string()), } diff --git a/xpuctl/src/list.rs b/xpuctl/src/list.rs index 85c5be1..a36a998 100644 --- a/xpuctl/src/list.rs +++ b/xpuctl/src/list.rs @@ -1,16 +1,17 @@ -use crate::redfish::{RedfishError, XPU}; +use libonm::xpu::{XPUError, BMC, XPU}; + use crate::types::Context; -pub async fn run(cxt: &Context) -> Result<(), RedfishError> { +pub async fn run(cxt: &Context) -> Result<(), XPUError> { println!( "{:<20}{:<10}{:<15}{:<10}{:<15}{:<15}{}", "ID", "Status", "Vendor", "FW", "SN", "BMC", "Address" ); for bmc in cxt.bmc.iter() { - let xpu = XPU::new(bmc).await?; + let xpu = XPU::new(&BMC::from(bmc)).await?; println!( "{:<20}{:<10}{:<15}{:<10}{:<15}{:<15}{}", - xpu.bmc.name, + bmc.name, xpu.status.to_string(), xpu.vendor, xpu.firmware_version, diff --git a/xpuctl/src/types.rs b/xpuctl/src/types.rs index bf686da..09fbc6c 100644 --- a/xpuctl/src/types.rs +++ b/xpuctl/src/types.rs @@ -16,3 +16,13 @@ pub struct Context { pub bmc: Vec, } + +impl From<&BMC> for libonm::xpu::BMC { + fn from(bmc: &BMC) -> Self { + libonm::xpu::BMC { + username: bmc.username.clone().unwrap(), + address: bmc.address.clone(), + password: bmc.password.clone().unwrap(), + } + } +}