1use 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 pub mtu_limit: u16,
30 pub service_level: u8,
32 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 pub guid: String,
46 pub index0: bool,
48 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 pub name: String,
78 pub pkey: PartitionKey,
80 pub ipoib: bool,
82 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 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 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 #[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}