test_wp/
lib.rs

1/*
2 * SPDX-FileCopyrightText: Copyright (c) 2021-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3 * SPDX-License-Identifier: LicenseRef-NvidiaProprietary
4 *
5 * NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
6 * property and proprietary rights in and to this material, related
7 * documentation and any modifications thereto. Any use, reproduction,
8 * disclosure or distribution of this material and related documentation
9 * without an express license agreement from NVIDIA CORPORATION or
10 * its affiliates is strictly prohibited.
11 */
12
13use std::collections::HashMap;
14
15use serde::{Deserialize, Serialize};
16use thiserror::Error;
17use url::Url;
18
19use self::rest::{RestClient, RestClientConfig, RestError, RestScheme};
20
21mod rest;
22
23const DEFAULT_INDEX0: bool = true;
24const DEFAULT_MEMBERSHIP: PortMembership = PortMembership::Full;
25
26#[derive(Serialize, Deserialize, Debug, Clone)]
27pub struct PartitionQoS {
28    // Default 2k; one of 2k or 4k; the MTU of the services.
29    pub mtu_limit: u16,
30    // Default is None, value can be range from 0-15
31    pub service_level: u8,
32    // Default is None, can be one of the following: 2.5, 10, 30, 5, 20, 40, 60, 80, 120, 14, 56, 112, 168, 25, 100, 200, or 300
33    pub rate_limit: f64,
34}
35
36#[derive(Serialize, Deserialize, Debug, Clone)]
37pub enum PortMembership {
38    Limited,
39    Full,
40}
41
42#[derive(Serialize, Deserialize, Debug)]
43pub struct PortConfig {
44    /// The GUID of Port.
45    pub guid: String,
46    /// Default false; store the PKey at index 0 of the PKey table of the GUID.
47    pub index0: bool,
48    /// Default is full:
49    ///   "full"    - members with full membership can communicate with all hosts (members) within the network/partition
50    ///   "limited" - members with limited membership cannot communicate with other members with limited membership.
51    ///               However, communication is allowed between every other combination of membership types.
52    pub membership: PortMembership,
53}
54
55impl From<&String> for PortConfig {
56    fn from(guid: &String) -> Self {
57        PortConfig {
58            guid: guid.clone(),
59            index0: DEFAULT_INDEX0,
60            membership: DEFAULT_MEMBERSHIP,
61        }
62    }
63}
64
65impl From<String> for PortConfig {
66    fn from(guid: String) -> Self {
67        PortConfig::from(&guid)
68    }
69}
70
71#[derive(Serialize, Deserialize, Debug, Clone)]
72pub struct PartitionKey(i32);
73
74#[derive(Serialize, Deserialize, Debug)]
75pub struct Partition {
76    /// The name of Partition.
77    pub name: String,
78    /// The pkey of Partition.
79    pub pkey: PartitionKey,
80    /// Default false
81    pub ipoib: bool,
82    /// The QoS of Partition.
83    pub qos: PartitionQoS,
84}
85
86#[derive(Serialize, Deserialize, Debug)]
87pub struct Port {
88    pub guid: String,
89    pub name: String,
90    #[serde(rename = "systemID")]
91    pub system_id: String,
92    pub lid: i32,
93    pub dname: String,
94    pub system_name: String,
95    pub physical_state: String,
96    pub logical_state: String,
97}
98
99#[derive(Serialize, Deserialize, Debug)]
100struct Pkey {
101    pkey: String,
102    ip_over_ib: bool,
103    membership: PortMembership,
104    index0: bool,
105    guids: Vec<String>,
106}
107
108#[derive(Default)]
109pub struct Filter {
110    pub guids: Option<Vec<String>>,
111    pub pkey: Option<PartitionKey>,
112}
113
114impl Filter {
115    fn valid(&self, p: &Port) -> bool {
116        // Check GUID filter
117        if let Some(guids) = &self.guids {
118            let mut found = false;
119            for id in guids {
120                if p.guid == *id {
121                    found = true;
122                    break;
123                }
124            }
125
126            if !found {
127                return false;
128            }
129        }
130
131        // All filters are passed, return true.
132        true
133    }
134}
135
136impl From<Vec<PortConfig>> for Filter {
137    fn from(guids: Vec<PortConfig>) -> Self {
138        let mut v = Vec::new();
139        for i in &guids {
140            v.push(i.guid.to_string());
141        }
142
143        Self {
144            guids: Some(v),
145            pkey: None,
146        }
147    }
148}
149
150impl TryFrom<i32> for PartitionKey {
151    type Error = UFMError;
152
153    fn try_from(pkey: i32) -> Result<Self, Self::Error> {
154        if pkey != (pkey & 0x7fff) {
155            return Err(UFMError::InvalidPKey(pkey.to_string()));
156        }
157
158        Ok(PartitionKey(pkey))
159    }
160}
161
162impl TryFrom<String> for PartitionKey {
163    type Error = UFMError;
164
165    fn try_from(pkey: String) -> Result<Self, Self::Error> {
166        let p = pkey.trim_start_matches("0x");
167        let k = i32::from_str_radix(p, 16);
168
169        match k {
170            Ok(v) => Ok(PartitionKey(v)),
171            Err(_e) => Err(UFMError::InvalidPKey(pkey.to_string())),
172        }
173    }
174}
175
176impl TryFrom<&String> for PartitionKey {
177    type Error = UFMError;
178
179    fn try_from(pkey: &String) -> Result<Self, Self::Error> {
180        PartitionKey::try_from(pkey.to_string())
181    }
182}
183
184impl TryFrom<&str> for PartitionKey {
185    type Error = UFMError;
186
187    fn try_from(pkey: &str) -> Result<Self, Self::Error> {
188        PartitionKey::try_from(pkey.to_string())
189    }
190}
191
192impl ToString for PartitionKey {
193    fn to_string(&self) -> String {
194        format!("0x{:x}", self.0)
195    }
196}
197
198impl From<PartitionKey> for i32 {
199    fn from(v: PartitionKey) -> i32 {
200        v.0
201    }
202}
203
204pub struct Ufm {
205    client: RestClient,
206}
207
208#[derive(Error, Debug)]
209pub enum UFMError {
210    #[error("{0}")]
211    Internal(String),
212    #[error("'{0}' not found")]
213    NotFound(String),
214    #[error("invalid pkey '{0}'")]
215    InvalidPKey(String),
216    #[error("invalid configuration '{0}'")]
217    InvalidConfig(String),
218}
219
220impl From<RestError> for UFMError {
221    fn from(e: RestError) -> Self {
222        match e {
223            RestError::Internal(msg) => UFMError::Internal(msg),
224            RestError::NotFound(msg) => UFMError::NotFound(msg),
225            RestError::AuthFailure(msg) => UFMError::InvalidConfig(msg),
226            RestError::InvalidConfig(msg) => UFMError::InvalidConfig(msg),
227        }
228    }
229}
230
231pub struct UFMConfig {
232    pub address: String,
233    pub username: Option<String>,
234    pub password: Option<String>,
235    pub token: Option<String>,
236}
237
238pub fn connect(conf: UFMConfig) -> Result<Ufm, UFMError> {
239    let addr = Url::parse(&conf.address)
240        .map_err(|_| UFMError::InvalidConfig("invalid UFM url".to_string()))?;
241    let address = addr
242        .host_str()
243        .ok_or(UFMError::InvalidConfig("invalid UFM host".to_string()))?;
244
245    let (base_path, auth_info) = match &conf.token {
246        None => {
247            let password = conf
248                .password
249                .clone()
250                .ok_or(UFMError::InvalidConfig("password is empty".to_string()))?;
251            let username = conf
252                .username
253                .clone()
254                .ok_or(UFMError::InvalidConfig("username is empty".to_string()))?;
255
256            (
257                "/ufmRest".to_string(),
258                base64::encode(format!("{}:{}", username, password)),
259            )
260        }
261        Some(t) => ("/ufmRestV3".to_string(), t.to_string()),
262    };
263
264    let c = RestClient::new(&RestClientConfig {
265        address: address.to_string(),
266        port: addr.port(),
267        auth_info,
268        base_path,
269        scheme: RestScheme::from(addr.scheme().to_string()),
270    })?;
271
272    Ok(Ufm { client: c })
273}
274
275impl Ufm {
276    pub async fn bind_ports(&self, p: Partition, ports: Vec<PortConfig>) -> Result<(), UFMError> {
277        let path = String::from("/resources/pkeys");
278
279        let mut membership = PortMembership::Full;
280        let mut index0 = true;
281
282        let mut guids = Vec::with_capacity(ports.len());
283        for pb in ports {
284            membership = pb.membership.clone();
285            index0 = pb.index0;
286            guids.push(pb.guid.to_string());
287        }
288
289        let pkey = Pkey {
290            pkey: p.pkey.clone().to_string(),
291            ip_over_ib: p.ipoib,
292            membership,
293            index0,
294            guids,
295        };
296
297        let data = serde_json::to_string(&pkey)
298            .map_err(|_| UFMError::InvalidConfig("invalid partition".to_string()))?;
299
300        self.client.post(&path, data).await?;
301
302        Ok(())
303    }
304
305    pub async fn unbind_ports(
306        &self,
307        pkey: PartitionKey,
308        guids: Vec<String>,
309    ) -> Result<(), UFMError> {
310        let path = String::from("/actions/remove_guids_from_pkey");
311
312        #[derive(Serialize, Deserialize, Debug)]
313        struct Pkey {
314            pkey: String,
315            guids: Vec<String>,
316        }
317
318        let pkey = Pkey {
319            pkey: pkey.clone().to_string(),
320            guids,
321        };
322
323        let data = serde_json::to_string(&pkey)
324            .map_err(|_| UFMError::InvalidConfig("invalid partition".to_string()))?;
325
326        self.client.post(&path, data).await?;
327
328        Ok(())
329    }
330
331    pub async fn get_partition(&self, pkey: &str) -> Result<Partition, UFMError> {
332        let pkey = PartitionKey::try_from(pkey)?;
333
334        let path = format!("/resources/pkeys/{}?qos_conf=true", pkey.to_string());
335
336        #[derive(Serialize, Deserialize, Debug)]
337        struct Pkey {
338            partition: String,
339            ip_over_ib: bool,
340            qos_conf: PartitionQoS,
341        }
342        let pk: Pkey = self.client.get(&path).await?;
343
344        Ok(Partition {
345            name: pk.partition,
346            pkey,
347            ipoib: pk.ip_over_ib,
348            qos: pk.qos_conf,
349        })
350    }
351
352    pub async fn list_partition(&self) -> Result<Vec<Partition>, UFMError> {
353        #[derive(Serialize, Deserialize, Debug)]
354        struct Pkey {
355            partition: String,
356            ip_over_ib: bool,
357            qos_conf: PartitionQoS,
358        }
359
360        let path = String::from("/resources/pkeys?qos_conf=true");
361        let pkey_qos: HashMap<String, Pkey> = self.client.list(&path).await?;
362
363        let mut parts = Vec::new();
364
365        for (k, v) in pkey_qos {
366            parts.push(Partition {
367                name: v.partition,
368                pkey: PartitionKey::try_from(&k)?,
369                ipoib: v.ip_over_ib,
370                qos: v.qos_conf.clone(),
371            });
372        }
373
374        Ok(parts)
375    }
376
377    pub async fn delete_partition(&self, pkey: &str) -> Result<(), UFMError> {
378        let path = format!("/resources/pkeys/{}", pkey);
379        self.client.delete(&path).await?;
380
381        Ok(())
382    }
383
384    async fn list_partition_ports(&self, pkey: &PartitionKey) -> Result<Vec<String>, UFMError> {
385        // get GUIDs from pkey
386        #[derive(Serialize, Deserialize, Debug)]
387        struct PkeyWithGUIDs {
388            pub partition: String,
389            pub ip_over_ib: bool,
390            pub guids: Vec<PortConfig>,
391        }
392
393        let path = format!("resources/pkeys/{}?guids_data=true", pkey.to_string());
394        let pkeywithguids: PkeyWithGUIDs = self.client.get(&path).await?;
395
396        let filter = Filter::from(pkeywithguids.guids);
397
398        Ok(filter.guids.unwrap_or(vec![]))
399    }
400
401    pub async fn list_port(&self, filter: Option<Filter>) -> Result<Vec<Port>, UFMError> {
402        let path = String::from("/resources/ports?sys_type=Computer");
403        let ports: Vec<Port> = self.client.list(&path).await?;
404
405        let mut f = filter.unwrap_or(Filter::default());
406        if let Some(pkey) = &f.pkey {
407            let mut ports = self.list_partition_ports(pkey).await?;
408            ports.extend(f.guids.unwrap_or(vec![]));
409            f.guids = Some(ports);
410        }
411
412        let mut res = Vec::new();
413        for port in ports {
414            if f.valid(&port) {
415                res.push(port);
416            }
417        }
418
419        Ok(res)
420    }
421
422    pub async fn version(&self) -> Result<String, UFMError> {
423        #[derive(Serialize, Deserialize, Debug)]
424        struct Version {
425            ufm_release_version: String,
426        }
427
428        let path = String::from("/app/ufm_version");
429        let v: Version = self.client.get(&path).await?;
430
431        Ok(v.ufm_release_version)
432    }
433}