1use crate::error::{MetricsResult, StorageError};
2use crate::ir::{TsPoint, TsValue};
3use crate::IntoPoint;
4use chrono::offset::Utc;
5use chrono::DateTime;
6use log::{error, trace};
7use reqwest::header::{HeaderMap, HeaderValue, ACCEPT};
8use serde::de::DeserializeOwned;
9use std::fmt::Debug;
27
28#[derive(Clone, Deserialize, Debug)]
29pub struct BrocadeConfig {
30 pub endpoint: String,
32 pub user: String,
33 pub password: String,
35 pub certificate: Option<String>,
38 pub root_certificate: Option<String>,
41 pub region: String,
43}
44
45pub struct Brocade {
46 client: reqwest::Client,
47 config: BrocadeConfig,
48 token: String,
49}
50
51impl Brocade {
52 pub fn new(client: &reqwest::Client, config: BrocadeConfig) -> MetricsResult<Self> {
54 let token = login(client, &config)?;
55 Ok(Brocade {
56 client: client.clone(),
57 config,
58 token,
59 })
60 }
61}
62
63impl Drop for Brocade {
64 fn drop(&mut self) {
65 if let Err(e) = self.logout() {
66 error!("logout failed: {}", e);
67 }
68 }
69}
70
71#[test]
72fn parse_resource_groups() {
73 use std::fs::File;
74 use std::io::Read;
75
76 let mut f = File::open("tests/brocade/resource_groups.json").unwrap();
77 let mut buff = String::new();
78 f.read_to_string(&mut buff).unwrap();
79
80 let i: ResourceGroups = serde_json::from_str(&buff).unwrap();
81 println!("result: {:#?}", i);
82}
83#[serde(rename_all = "camelCase")]
84#[derive(Deserialize, Debug)]
85pub struct ResourceGroups {
86 pub resource_groups: Vec<ResourceGroup>,
87}
88
89#[derive(Deserialize, Debug)]
90pub struct ResourceGroup {
91 pub key: String,
92 pub name: String,
93 #[serde(rename = "type")]
94 pub resource_type: String,
95}
96
97#[serde(rename_all = "camelCase")]
98#[derive(Deserialize, Debug)]
99pub struct BufferCredit {
100 pub bb_credit: u64,
101 #[serde(rename = "peerBBCredit")]
102 pub peer_bb_credit: u64,
103 pub round_trip_time: u64,
104}
105
106#[test]
107fn parse_fc_fabrics() {
108 use std::fs::File;
109 use std::io::Read;
110
111 sleep_the_collections();
112
113 let mut f = File::open("tests/brocade/fcfabrics.json").unwrap();
114 let mut buff = String::new();
115 f.read_to_string(&mut buff).unwrap();
116
117 let i: FcFabrics = serde_json::from_str(&buff).unwrap();
118 println!("result: {:#?}", i);
119}
120
121#[serde(rename_all = "camelCase")]
122#[derive(Deserialize, Debug)]
123pub struct FcFabrics {
124 pub fc_fabrics: Vec<FcFabric>,
125 pub start_index: Option<i32>,
126 pub items_per_page: Option<i32>,
127 pub total_results: Option<u64>,
128}
129
130#[serde(rename_all = "camelCase")]
131#[derive(Deserialize, Debug, IntoPoint)]
132pub struct FcFabric {
133 pub key: String,
134 pub seed_switch_wwn: String,
135 pub name: String,
136 pub secure: bool,
137 pub ad_environment: bool,
138 pub contact: Option<String>,
139 pub location: Option<String>,
140 pub description: Option<String>,
141 pub principal_switch_wwn: String,
142 pub fabric_name: String,
143 pub virtual_fabric_id: i32,
144 pub seed_switch_ip_address: String,
145}
146
147#[serde(rename_all = "camelCase")]
148#[derive(Deserialize, Debug)]
149pub struct FcPorts {
150 pub fc_ports: Vec<FcPort>,
151 pub start_index: Option<i32>,
152 pub items_per_page: Option<i32>,
153 pub total_results: Option<u64>,
154}
155
156#[test]
157fn parse_fc_ports() {
158 use std::fs::File;
159 use std::io::Read;
160
161 let mut f = File::open("tests/brocade/fcports.json").unwrap();
162 let mut buff = String::new();
163 f.read_to_string(&mut buff).unwrap();
164
165 let i: FcPorts = serde_json::from_str(&buff).unwrap();
166 println!("result: {:#?}", i);
167}
168
169#[serde(rename_all = "camelCase")]
170#[derive(Deserialize, Debug, IntoPoint)]
171pub struct FcPort {
172 key: String,
173 wwn: String,
174 name: String,
175 slot_number: u64,
176 port_number: u64,
177 user_port_number: u64,
178 port_id: String,
179 port_index: u64,
180 area_id: u64,
181 #[serde(rename = "type")]
182 port_type: String,
183 status: String,
184 status_message: String,
185 locked_port_type: String,
186 speed: String,
187 speeds_supported: String,
188 max_port_speed: u16,
189 desired_credits: u64,
190 buffer_allocated: u64,
191 estimated_distance: u64,
192 actual_distance: u64,
193 long_distance_setting: u64,
194 remote_node_wwn: String,
195 remote_port_wwn: String,
196 licensed: bool,
197 swapped: bool,
198 trunked: bool,
199 trunk_master: bool,
200 persistently_disabled: bool,
201 ficon_supported: bool,
202 blocked: bool,
203 prohibit_port_numbers: Option<String>,
204 prohibit_port_count: u64,
205 npiv_capable: bool,
206 npiv_enabled: bool,
207 fc_fast_write_enabled: bool,
208 isl_rrdy_enabled: bool,
209 rate_limit_capable: bool,
210 rate_limited: bool,
211 qos_capable: bool,
212 qos_enabled: bool,
213 fcr_fabric_id: u64,
214 state: String,
215 occupied: bool,
216 master_port_number: i64,
217}
218
219#[serde(rename_all = "camelCase")]
220#[derive(Deserialize, Debug)]
221pub struct Fec {
222 pub corrected_blocks: u64,
223 pub uncorrected_blocks: u64,
224}
225
226#[test]
227fn parse_fc_switches() {
228 use std::fs::File;
229 use std::io::Read;
230
231 let mut f = File::open("tests/brocade/fcswitches.json").unwrap();
232 let mut buff = String::new();
233 f.read_to_string(&mut buff).unwrap();
234
235 let i: FcSwitches = serde_json::from_str(&buff).unwrap();
236 println!("result: {:#?}", i);
237}
238
239#[serde(rename_all = "camelCase")]
240#[derive(Deserialize, Debug)]
241pub struct FcSwitches {
242 pub fc_switches: Vec<FcSwitch>,
243 pub start_index: Option<i32>,
244 pub items_per_page: Option<i32>,
245 pub total_results: Option<u64>,
246}
247
248#[serde(rename_all = "camelCase")]
249#[derive(Deserialize, Debug, IntoPoint)]
250pub struct FcSwitch {
251 pub key: String,
252 #[serde(rename = "type")]
253 pub fc_type: u64,
254 pub name: String,
255 pub wwn: String,
256 pub virtual_fabric_id: i64,
257 pub domain_id: u64,
258 pub base_switch: bool,
259 pub role: String,
260 pub fcs_role: String,
261 pub ad_capable: bool,
262 pub operational_status: String,
263 pub state: String,
264 pub status_reason: Option<String>,
265 pub lf_enabled: bool,
266 pub default_logical_switch: bool,
267 pub fms_mode: bool,
268 pub dynamic_load_sharing_capable: bool,
269 pub port_based_routing_present: bool,
270 pub in_order_delivery_capable: bool,
271 pub persistent_did_enabled: bool,
272 pub auto_snmp_enabled: bool,
273}
274
275pub enum FabricTimeSeries {
276 MemoryUtilPercentage,
277 CpuUtilPercentage,
278 Temperature,
279 FanSpeed,
280 ResponseTime,
281 SystemUpTime,
282 PortsNotInUse,
283 PingPktLossPercentage,
284}
285
286impl ToString for FabricTimeSeries {
287 fn to_string(&self) -> String {
288 match *self {
289 FabricTimeSeries::MemoryUtilPercentage => "timeseriesmemoryutilpercentage".into(),
290 FabricTimeSeries::CpuUtilPercentage => "timeseriescpuutilpercentage".into(),
291 FabricTimeSeries::Temperature => "timeseriestemperature".into(),
292 FabricTimeSeries::FanSpeed => "timeseriesfanspeed".into(),
293 FabricTimeSeries::ResponseTime => "timeseriesresponsetime".into(),
294 FabricTimeSeries::SystemUpTime => "timeseriessystemuptime".into(),
295 FabricTimeSeries::PortsNotInUse => "timeseriesportsnotinuse".into(),
296 FabricTimeSeries::PingPktLossPercentage => "timeseriespingpktlosspercentage".into(),
297 }
298 }
299}
300
301pub enum FcIpTimeSeries {
302 CompressionRatio,
303 Latency,
304 DroppedPackets,
305 LinkRetransmits,
306 TimeoutRetransmits,
307 FastRetransmits,
308 DupAckRecvd,
309 WindowSizeRtt,
310 TcpOooSegments,
311 SlowStartStatusErrors,
312 RealtimeCompressionRatio,
313}
314
315impl ToString for FcIpTimeSeries {
316 fn to_string(&self) -> String {
317 match *self {
318 FcIpTimeSeries::CompressionRatio => "timeseriescompressionratio".into(),
319 FcIpTimeSeries::Latency => "timeserieslatency".into(),
320 FcIpTimeSeries::DroppedPackets => "timeseriesdroppedpackets".into(),
321 FcIpTimeSeries::LinkRetransmits => "timeserieslinkretransmits".into(),
322 FcIpTimeSeries::TimeoutRetransmits => "timeseriestimeoutretransmits".into(),
323 FcIpTimeSeries::FastRetransmits => "timeseriesfastretransmits".into(),
324 FcIpTimeSeries::DupAckRecvd => "timeseriesdupackrecvd".into(),
325 FcIpTimeSeries::WindowSizeRtt => "timeserieswindowsizertt".into(),
326 FcIpTimeSeries::TcpOooSegments => "timeseriestcpooosegments".into(),
327 FcIpTimeSeries::SlowStartStatusErrors => "timeseriesslowstartstatuserrors".into(),
328 FcIpTimeSeries::RealtimeCompressionRatio => "timeseriesrealtimecompressionratio".into(),
329 }
330 }
331}
332pub enum FcTimeSeries {
333 UtilPercentage,
334 Traffic,
335 CrcErrors,
336 LinkResets,
337 SignalLosses,
338 SyncLosses,
339 LinkFailures,
340 SequenceErrors,
341 InvalidTransmissions,
342 C3Discards,
343 C3DiscardsTxTo,
344 C3DiscardsRxTo,
345 C3DiscardsUnreachable,
346 C3DiscardsOther,
347 EncodeErrorOut,
348 SfpPower,
349 SfpVoltage,
350 SfpCurrent,
351 SfpTemperature,
352 InvalidOrderedSets,
353 BbCreditZero,
354 TruncatedFrames,
355}
356
357impl ToString for FcTimeSeries {
358 fn to_string(&self) -> String {
359 match *self {
360 FcTimeSeries::UtilPercentage => "timeseriesutilpercentage".into(),
361 FcTimeSeries::Traffic => "timeseriestraffic".into(),
362 FcTimeSeries::CrcErrors => "timeseriescrcerrors".into(),
363 FcTimeSeries::LinkResets => "timeserieslinkresets".into(),
364 FcTimeSeries::SignalLosses => "timeseriessignallosses".into(),
365 FcTimeSeries::SyncLosses => "timeseriessynclosses".into(),
366 FcTimeSeries::LinkFailures => "timeserieslinkfailures".into(),
367 FcTimeSeries::SequenceErrors => "timeseriessequenceerrors".into(),
368 FcTimeSeries::InvalidTransmissions => "timeseriesinvalidtransmissions".into(),
369 FcTimeSeries::C3Discards => "timeseriesc3discards".into(),
370 FcTimeSeries::C3DiscardsTxTo => "timeseriesc3discardstxto".into(),
371 FcTimeSeries::C3DiscardsRxTo => "timeseriesc3discardsrxto".into(),
372 FcTimeSeries::C3DiscardsUnreachable => "timeseriesc3discardsunreachable".into(),
373 FcTimeSeries::C3DiscardsOther => "timeseriesc3discardsother".into(),
374 FcTimeSeries::EncodeErrorOut => "timeseriesencodeerrorout".into(),
375 FcTimeSeries::SfpPower => "timeseriessfppower".into(),
376 FcTimeSeries::SfpVoltage => "timeseriessfpvoltage".into(),
377 FcTimeSeries::SfpCurrent => "timeseriessfpcurrent".into(),
378 FcTimeSeries::SfpTemperature => "timeseriessfptemperature".into(),
379 FcTimeSeries::InvalidOrderedSets => "timeseriesinvalidorderedsets".into(),
380 FcTimeSeries::BbCreditZero => "timeseriesbbcreditzero".into(),
381 FcTimeSeries::TruncatedFrames => "timeseriestruncatedframes".into(),
382 }
383 }
384}
385
386pub enum FrameTimeSeries {
387 TxFrameCount,
388 RxFrameCount,
389 TxFrameRate,
390 RxFrameRate,
391 TxWordCount,
392 RxWordCount,
393 TxThroughput,
394 RxThroughput,
395 AvgTxFrameSize,
396 AvgRxFrameSize,
397 GeneratorTxFrameCount,
398 GeneratorRxFrameCount,
399 MirroredFramesCount,
400 MirroredTxFrames,
401 MirroredRxFrames,
402}
403
404impl ToString for FrameTimeSeries {
405 fn to_string(&self) -> String {
406 match *self {
407 FrameTimeSeries::TxFrameCount => "timeseriestxframecount".into(),
408 FrameTimeSeries::RxFrameCount => "timeseriesrxframecount".into(),
409 FrameTimeSeries::TxFrameRate => "timeseriestxframerate".into(),
410 FrameTimeSeries::RxFrameRate => "timeseriesrxframerate".into(),
411 FrameTimeSeries::TxWordCount => "timeseriestxwordcount".into(),
412 FrameTimeSeries::RxWordCount => "timeseriesrxwordcount".into(),
413 FrameTimeSeries::TxThroughput => "timeseriestxthroughput".into(),
414 FrameTimeSeries::RxThroughput => "timeseriesrxthroughput".into(),
415 FrameTimeSeries::AvgTxFrameSize => "timeseriesavgtxframesize".into(),
416 FrameTimeSeries::AvgRxFrameSize => "timeseriesavgrxframesize".into(),
417 FrameTimeSeries::GeneratorTxFrameCount => "timeseriesgeneratortxframecount".into(),
418 FrameTimeSeries::GeneratorRxFrameCount => "timeseriesgeneratorrxframecount".into(),
419 FrameTimeSeries::MirroredFramesCount => "timeseriesmirroredframescount".into(),
420 FrameTimeSeries::MirroredTxFrames => "timeseriesmirroredtxframes".into(),
421 FrameTimeSeries::MirroredRxFrames => "timeseriesmirroredrxframes".into(),
422 }
423 }
424}
425
426pub struct ReadDiagnostic {
427 pub switch_name: String,
428 pub switch_wwn: String,
429 pub number_of_ports: u64,
430 pub stats_type: RdpStatsType,
431 pub port_wwn: String,
432 pub port_type: String,
433 pub node_wwn: String,
434 pub tx_power: String,
435 pub rx_power: String,
436 pub temperature: String,
437 pub sfp_type: String,
438 pub laser_type: String,
439 pub voltage: String,
440 pub current: String,
441 pub connecter_type: String,
442 pub supported_speeds: String,
443 pub link_failure: u64,
444 pub loss_of_sync: u64,
445 pub loss_of_signal: u64,
446 pub protocol_error: u64,
447 pub invalid_word: u64,
448 pub invalid_crc: u64,
449 pub fec: Fec,
450 pub buffer_credit: BufferCredit,
451}
452
453pub enum RdpStatsType {
454 Historical,
455 Realtime,
456}
457
458pub enum ScsiTimeSeries {
459 ReadFrameCount,
460 WriteFrameCount,
461 ReadFrameRate,
462 WriteFrameRate,
463 ReadData,
464 WriteData,
465 ReadDataRate,
466 WriteDataRate,
467}
468
469impl ToString for ScsiTimeSeries {
470 fn to_string(&self) -> String {
471 match *self {
472 ScsiTimeSeries::ReadFrameCount => "timeseriesscsireadframecount".into(),
473 ScsiTimeSeries::WriteFrameCount => "timeseriesscsiwriteframecount".into(),
474 ScsiTimeSeries::ReadFrameRate => "timeseriesscsireadframerate".into(),
475 ScsiTimeSeries::WriteFrameRate => "timeseriesscsiwriteframerate".into(),
476 ScsiTimeSeries::ReadData => "timeseriesscsireaddata".into(),
477 ScsiTimeSeries::WriteData => "timeseriesscsiwritedata".into(),
478 ScsiTimeSeries::ReadDataRate => "timeseriesscsireaddatarate".into(),
479 ScsiTimeSeries::WriteDataRate => "timeseriesscsiwritedatarate".into(),
480 }
481 }
482}
483
484pub enum TimeSeries {
485 Fc(FcTimeSeries),
486 FcIp(FcIpTimeSeries),
487}
488
489pub fn login(client: &reqwest::Client, config: &BrocadeConfig) -> MetricsResult<String> {
491 let mut headers = HeaderMap::new();
492 headers.insert(
493 ACCEPT,
494 HeaderValue::from_str("application/vnd.brocade.networkadvisor+json;version=v1")?,
495 );
496 headers.insert("WSUsername", HeaderValue::from_str(&config.user)?);
497 headers.insert("WSPassword", HeaderValue::from_str(&config.password)?);
498
499 let resp = client
500 .post(&format!(
501 "{}://{}/rest/login",
502 match config.certificate {
503 Some(_) => "https",
504 None => "http",
505 },
506 config.endpoint
507 ))
508 .headers(headers)
509 .send()?
510 .error_for_status()?;
511
512 let token = resp.headers().get("WStoken");
515 match token {
516 Some(data) => Ok(data.to_str()?.to_owned()),
517 None => Err(StorageError::new(format!(
518 "WSToken multiple lines. {:?}. Please check server",
519 token
520 ))),
521 }
522}
523
524fn sleep_the_collections() {
529 use std::{thread, time};
530
531 let sleep_time = time::Duration::from_millis(5000);
532 let now = time::Instant::now();
533
534 thread::sleep(sleep_time);
535
536 assert!(now.elapsed() >= sleep_time);
537}
538
539impl Brocade {
540 pub fn logout(&self) -> MetricsResult<()> {
542 let mut headers = HeaderMap::new();
543 headers.insert("WStoken", HeaderValue::from_str(&self.token)?);
544
545 self.client
546 .post(&format!(
547 "{}://{}/rest/logout",
548 match self.config.certificate {
549 Some(_) => "https",
550 None => "http",
551 },
552 self.config.endpoint
553 ))
554 .headers(headers)
555 .send()?
556 .error_for_status()?;
557 Ok(())
558 }
559
560 fn get_server_response<T>(&self, api_call: &str, ws_token: &str) -> MetricsResult<T>
561 where
562 T: DeserializeOwned + Debug,
563 {
564 let url = format!(
565 "{}://{}/rest/{}",
566 match self.config.certificate {
567 Some(_) => "https",
568 None => "http",
569 },
570 self.config.endpoint,
571 api_call
572 );
573 let resp = self
574 .client
575 .get(&url)
576 .header(
577 ACCEPT,
578 "application/vnd.brocade.networkadvisor+json;version=v1",
579 )
580 .header("WStoken", HeaderValue::from_str(ws_token)?)
581 .send()?
582 .error_for_status()?
583 .text()?;
584 trace!("server returned: {}", resp);
585 let json: Result<T, serde_json::Error> = serde_json::from_str(&resp);
586 trace!("json result: {:?}", json);
587 Ok(json?)
588 }
589
590 pub fn get_fc_fabrics(&self, t: DateTime<Utc>) -> MetricsResult<Vec<TsPoint>> {
591 sleep_the_collections();
592 let result =
593 self.get_server_response::<FcFabrics>("resourcegroups/All/fcfabrics", &self.token)?;
594 let mut points = result
595 .fc_fabrics
596 .iter()
597 .flat_map(|fabric| fabric.into_point(Some("brocade_fc_fabric"), true))
598 .collect::<Vec<TsPoint>>();
599 for point in &mut points {
600 point.timestamp = Some(t)
601 }
602 Ok(points)
603 }
604
605 pub fn get_fc_switch_timeseries(
606 &self,
607 switch_key: &str,
608 timeseries: TimeSeries,
609 ) -> MetricsResult<()> {
610 sleep_the_collections();
612 let _url = format!(
613 "resourcegroups/All/fcswitches/{}/{}?duration=360",
614 switch_key,
615 match timeseries {
616 TimeSeries::Fc(ts) => ts.to_string(),
617 TimeSeries::FcIp(ts) => ts.to_string(),
618 }
619 );
620 Ok(())
621 }
622
623 pub fn get_fc_fabric_timeseries(
624 &self,
625 fabric_key: &str,
626 timeseries: &FabricTimeSeries,
627 ) -> MetricsResult<()> {
628 sleep_the_collections();
630 let _url = format!(
631 "resourcegroups/All/fcfabrics/{}/{}?duration=360",
632 fabric_key,
633 timeseries.to_string(),
634 );
635
636 Ok(())
637 }
638
639 pub fn get_fc_fabric_ids(&self) -> MetricsResult<Vec<String>> {
640 sleep_the_collections();
641 let result = self
642 .get_server_response::<FcFabrics>("resourcegroups/All/fcfabrics", &self.token)
643 .map(|fabrics| {
644 let fabrics: Vec<String> = fabrics
645 .fc_fabrics
646 .iter()
647 .map(|fabric| fabric.key.clone())
648 .collect::<Vec<String>>();
649 fabrics
650 })?;
651 Ok(result)
652 }
653
654 pub fn get_fc_ports(&self, fabric_key: &str, t: DateTime<Utc>) -> MetricsResult<Vec<TsPoint>> {
655 sleep_the_collections();
656 let result = self.get_server_response::<FcPorts>(
657 &format!("resourcegroups/All/fcswitches/{}/fcports", fabric_key),
658 &self.token,
659 )?;
660 let mut points = result
661 .fc_ports
662 .iter()
663 .flat_map(|port| port.into_point(Some("brocade_fc_port"), true))
664 .collect::<Vec<TsPoint>>();
665 for point in &mut points {
666 point.timestamp = Some(t)
667 }
668 Ok(points)
669 }
670
671 pub fn get_fc_switch_ids(&self) -> MetricsResult<Vec<String>> {
672 sleep_the_collections();
673 let result = self
674 .get_server_response::<FcSwitches>("resourcegroups/All/fcswitches", &self.token)
675 .map(|switches| {
676 let switches: Vec<String> = switches
677 .fc_switches
678 .iter()
679 .map(|switch| switch.key.clone())
680 .collect::<Vec<String>>();
681 switches
682 })?;
683 Ok(result)
684 }
685
686 pub fn get_fc_switches(&self, t: DateTime<Utc>) -> MetricsResult<Vec<TsPoint>> {
687 sleep_the_collections();
688 let result =
689 self.get_server_response::<FcSwitches>("resourcegroups/All/fcswitches", &self.token)?;
690 let mut points = result
691 .fc_switches
692 .iter()
693 .flat_map(|switch| switch.into_point(Some("brocade_fc_switch"), true))
694 .collect::<Vec<TsPoint>>();
695 for point in &mut points {
696 point.timestamp = Some(t)
697 }
698 Ok(points)
699 }
700
701 pub fn get_resource_groups(&self) -> MetricsResult<ResourceGroups> {
702 let result = self.get_server_response::<ResourceGroups>("resourcegroups", &self.token)?;
703 Ok(result)
704 }
705}