libstorage/
solidfire.rs

1/**
2* Copyright 2019 Comcast Cable Communications Management, LLC
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8* http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*
16* SPDX-License-Identifier: Apache-2.0
17*/
18use std::collections::HashMap;
19use std::fmt::Debug;
20
21use crate::error::MetricsResult;
22use crate::ir::{TsPoint, TsValue};
23use crate::IntoPoint;
24
25use chrono::offset::Utc;
26use chrono::DateTime;
27use log::debug;
28use serde::de::DeserializeOwned;
29use uuid::Uuid;
30
31#[derive(Clone, Deserialize, Debug)]
32pub struct SolidfireConfig {
33    /// The solidfire endpoint to use
34    pub endpoint: String,
35    pub user: String,
36    pub password: String,
37    /// Optional certificate file to use against the server
38    /// der encoded
39    pub certificate: Option<String>,
40    /// The region this cluster is located in
41    pub region: String,
42}
43
44pub struct Solidfire {
45    client: reqwest::Client,
46    config: SolidfireConfig,
47}
48
49impl Solidfire {
50    pub fn new(client: &reqwest::Client, config: SolidfireConfig) -> Self {
51        Solidfire {
52            client: client.clone(),
53            config,
54        }
55    }
56}
57
58#[derive(Debug, Deserialize, IntoPoint)]
59#[serde(rename_all = "camelCase")]
60pub struct AddressBlock {
61    pub available: String,
62    pub size: u64,
63    pub start: String,
64}
65
66#[derive(Debug, Deserialize, IntoPoint)]
67#[serde(rename_all = "camelCase")]
68pub struct Cluster {
69    pub cipi: String,
70    pub cluster: String,
71    pub encryption_capable: bool,
72    pub ensemble: Vec<String>,
73    pub mipi: String,
74    pub name: String,
75    #[serde(rename = "nodeID")]
76    pub node_id: String,
77    #[serde(rename = "pendingNodeID")]
78    pub pending_node_id: u64,
79    pub role: u64,
80    pub sipi: String,
81    pub state: ClusterState,
82    pub version: String,
83}
84
85#[derive(Debug, Deserialize, IntoPoint)]
86#[serde(rename_all = "camelCase")]
87pub struct ClusterCapacity {
88    active_block_space: u64,
89    active_sessions: u64,
90    #[serde(rename = "averageIOPS")]
91    average_iops: u64,
92    #[serde(rename = "clusterRecentIOSize")]
93    cluster_recent_io_size: u64,
94    #[serde(rename = "currentIOPS")]
95    current_iops: u64,
96    #[serde(rename = "maxIOPS")]
97    max_iops: u64,
98    max_over_provisionable_space: u64,
99    max_provisioned_space: u64,
100    max_used_metadata_space: u64,
101    max_used_space: u64,
102    non_zero_blocks: u64,
103    peak_active_sessions: u64,
104    #[serde(rename = "peakIOPS")]
105    peak_iops: u64,
106    provisioned_space: u64,
107    timestamp: String,
108    total_ops: u64,
109    unique_blocks: u64,
110    unique_blocks_used_space: u64,
111    used_metadata_space: u64,
112    used_metadata_space_in_snapshots: u64,
113    used_space: u64,
114    zero_blocks: u64,
115}
116
117#[derive(Debug, Deserialize)]
118#[serde(rename_all = "camelCase")]
119pub struct ClusterCapacityResult {
120    cluster_capacity: ClusterCapacity,
121}
122
123impl IntoPoint for ClusterCapacityResult {
124    fn into_point(&self, name: Option<&str>, is_time_series: bool) -> Vec<TsPoint> {
125        self.cluster_capacity.into_point(name, is_time_series)
126    }
127}
128
129#[derive(Debug, Deserialize, IntoPoint)]
130#[serde(rename_all = "camelCase")]
131pub struct ClusterFullThreshold {
132    pub block_fullness: FullnessStatus,
133    pub fullness: String,
134    pub max_metadata_over_provision_factor: u16,
135    pub metadata_fullness: FullnessStatus,
136    pub slice_reserve_used_threshold_pct: u16,
137    pub stage2_aware_threshold: u16,
138    pub stage2_block_threshold_bytes: u64,
139    pub stage3_block_threshold_bytes: u64,
140    pub stage3_block_threshold_percent: u16,
141    pub stage3_low_threshold: i64,
142    pub stage4_block_threshold_bytes: u64,
143    pub stage4_critical_threshold: i64,
144    pub stage5_block_threshold_bytes: u64,
145    pub sum_total_cluster_bytes: u64,
146    pub sum_total_metadata_cluster_bytes: u64,
147    pub sum_used_cluster_bytes: u64,
148    pub sum_used_metadata_cluster_bytes: u64,
149}
150
151#[derive(Debug, Deserialize, IntoPoint)]
152#[serde(rename_all = "camelCase")]
153pub struct ClusterInfo {
154    pub attributes: HashMap<String, String>,
155    pub encryption_at_rest_state: EncryptionState,
156    pub ensemble: Vec<String>,
157    pub mvip: String,
158    pub mvip_interface: String,
159    #[serde(rename = "mvipNodeID")]
160    pub mvip_node_id: u16,
161    pub mvip_vlan_tag: String,
162    pub name: String,
163    pub rep_count: u16,
164    pub svip: String,
165    pub svip_interface: String,
166    #[serde(rename = "svipNodeID")]
167    pub svip_node_id: u16,
168    pub svip_vlan_tag: String,
169    #[serde(rename = "uniqueID")]
170    pub unique_id: String,
171    pub uuid: Uuid,
172}
173
174#[derive(Debug, Deserialize)]
175#[serde(rename_all = "camelCase")]
176pub struct ClusterInfoResult {
177    pub cluster_info: ClusterInfo,
178}
179
180impl IntoPoint for ClusterInfoResult {
181    fn into_point(&self, name: Option<&str>, is_time_series: bool) -> Vec<TsPoint> {
182        self.cluster_info.into_point(name, is_time_series)
183    }
184}
185
186#[derive(Debug, Deserialize)]
187pub enum ClusterState {
188    Available,
189    Pending,
190    Active,
191    PendingActive,
192}
193
194#[derive(Debug, Deserialize, IntoPoint)]
195#[serde(rename_all = "camelCase")]
196pub struct ClusterStat {
197    #[serde(rename = "actualIOPS")]
198    pub actual_iops: u64,
199    #[serde(rename = "averageIOPSize")]
200    pub average_iop_size: u64,
201    pub client_queue_depth: u64,
202    pub cluster_utilization: f64,
203    pub latency_u_sec: u64,
204    #[serde(rename = "normalizedIOPS")]
205    pub normalized_iops: u64,
206    // Total cumulative bytes read from the cluster since creation
207    pub read_bytes: u64,
208    pub read_bytes_last_sample: u64,
209    pub read_latency_u_sec: u64,
210    pub read_latency_u_sec_total: u64,
211    pub read_ops: u64,
212    pub read_ops_last_sample: u64,
213    pub sample_period_msec: u64,
214    pub services_count: u64,
215    pub services_total: u64,
216    pub timestamp: String,
217    pub unaligned_reads: u64,
218    pub unaligned_writes: u64,
219    pub write_bytes: u64,
220    pub write_bytes_last_sample: u64,
221    pub write_latency_u_sec: u64,
222    pub write_latency_u_sec_total: u64,
223    pub write_ops: u64,
224    pub write_ops_last_sample: u64,
225}
226
227#[derive(Debug, Deserialize)]
228#[serde(rename_all = "camelCase")]
229pub struct ClusterStatsResult {
230    pub cluster_stats: ClusterStat,
231}
232
233impl IntoPoint for ClusterStatsResult {
234    fn into_point(&self, name: Option<&str>, is_time_series: bool) -> Vec<TsPoint> {
235        self.cluster_stats.into_point(name, is_time_series)
236    }
237}
238
239#[derive(Debug, Deserialize, IntoPoint)]
240#[serde(rename_all = "camelCase")]
241pub struct Drive {
242    pub attributes: HashMap<String, String>,
243    pub capacity: u64,
244    pub classic_slot: String,
245    #[serde(rename = "driveID")]
246    pub drive_id: u64,
247    #[serde(rename = "nodeID")]
248    pub node_id: u64,
249    pub serial: String,
250    pub status: DriveStatus,
251    #[serde(rename = "drive")]
252    pub drive_type: DriveType,
253}
254
255#[derive(Debug, Deserialize)]
256#[serde(rename_all = "camelCase")]
257pub enum DriveStatus {
258    Available,
259    Active,
260    Erasing,
261    Failed,
262    Removing,
263}
264
265#[derive(Debug, Deserialize)]
266#[serde(rename_all = "camelCase")]
267pub enum DriveType {
268    Block,
269    Unknown,
270    Volume,
271}
272
273#[derive(Debug, Deserialize)]
274#[serde(rename_all = "camelCase")]
275pub enum EncryptionState {
276    Enabling,
277    Enabled,
278    Disabling,
279    Disabled,
280}
281
282#[derive(Debug, Deserialize)]
283#[serde(rename_all = "camelCase")]
284pub enum FullnessStatus {
285    Stage1Happy,
286    Stage2Aware,
287    Stage3Low,
288    Stage4Critical,
289    Stage5CompletelyConsumed,
290}
291
292#[derive(Debug, Deserialize, IntoPoint)]
293#[serde(rename_all = "camelCase")]
294pub struct MetadataHosts {
295    dead_secondaries: Vec<u64>,
296    live_secondaries: Vec<u64>,
297    primary: u64,
298}
299
300#[derive(Debug, Deserialize, IntoPoint)]
301#[serde(rename_all = "camelCase")]
302pub struct Node {
303    #[serde(rename = "associatedFServiceID")]
304    pub associated_f_service_id: u64,
305    #[serde(rename = "associatedMasterServiceID")]
306    pub associated_master_service_id: u64,
307    pub attributes: HashMap<String, String>,
308    pub cip: String,
309    pub cipi: String,
310    pub fibre_channel_target_port_group: Option<u64>,
311    pub mip: String,
312    pub mipi: String,
313    pub name: String,
314    #[serde(rename = "nodeID")]
315    pub node_id: u64,
316    pub node_slot: String,
317    pub platform_info: PlatformInfo,
318    #[serde(rename = "protocolEndpointIDs")]
319    pub protocol_endpoints_ids: Option<Vec<Uuid>>,
320    pub sip: String,
321    pub sipi: String,
322    pub software_version: String,
323    pub uuid: Uuid,
324    pub virtual_networks: Vec<VirtualNetwork>,
325}
326
327#[derive(Debug, Deserialize)]
328pub struct Nodes {
329    pub nodes: Vec<Node>,
330}
331
332#[derive(Debug, Deserialize, IntoPoint)]
333#[serde(rename_all = "camelCase")]
334pub struct NodeStats {
335    pub count: u64,
336    pub cpu: u16,
337    pub cpu_total: u64,
338    // Bytes in on the cluster interface
339    pub c_bytes_in: u64,
340    // Bytes out on the cluster interface
341    pub c_bytes_out: u64,
342    // Bytes in on the storage interface
343    pub s_bytes_in: u64,
344    // Bytes out on the storage interface
345    pub s_bytes_out: u64,
346    // Bytes in on the management interface
347    pub m_bytes_in: u64,
348    // Bytes out on the management interface
349    pub m_bytes_out: u64,
350    pub network_utilization_cluster: u16,
351    pub network_utilization_storage: u16,
352    pub read_latency_u_sec_total: u64,
353    pub read_ops: u64,
354    pub timestamp: String,
355    pub used_memory: u64,
356    pub write_latency_u_sec_total: u64,
357    pub write_ops: u64,
358}
359
360#[derive(Debug, Deserialize, IntoPoint)]
361#[serde(rename_all = "camelCase")]
362pub struct PlatformInfo {
363    pub chassis_type: String,
364    pub cpu_model: String,
365    #[serde(rename = "nodeMemoryGB")]
366    pub node_memory_gb: u64,
367    pub node_type: String,
368    pub platform_config_version: String,
369}
370
371#[derive(Debug, Deserialize)]
372pub enum ReplicationMode {
373    Async,
374    Sync,
375    SnapshotsOnly,
376}
377
378#[derive(Debug, Deserialize, IntoPoint)]
379#[serde(rename_all = "camelCase")]
380pub struct RemoteReplication {
381    pub mode: ReplicationMode,
382    pub pause_limit: u64,
383    #[serde(rename = "remoteServiceID")]
384    pub remote_service_id: u64,
385    pub resume_details: Option<String>,
386    pub snapshot_replication: SnapshotReplication,
387    pub state: String,
388    pub state_details: Option<String>,
389}
390
391#[derive(Debug, Deserialize)]
392pub enum RemoveStatus {
393    Present,
394    NotPresent,
395    Syncing,
396    Deleted,
397}
398
399#[derive(Debug, Deserialize, IntoPoint)]
400pub struct Snapshot {
401    pub attributes: HashMap<String, String>,
402    pub checksum: String,
403    pub create_time: String,
404    pub enable_remote_replication: String,
405    pub expiration_reason: String,
406    pub expiration_time: String,
407    #[serde(rename = "groupID")]
408    pub group_id: u64,
409    #[serde(rename = "groupSnapshotUUID")]
410    pub group_snapshot_uuid: Uuid,
411    pub name: String,
412    pub remote_status: RemoveStatus,
413    #[serde(rename = "snapshotID")]
414    pub snapshot_id: String,
415    #[serde(rename = "snapshotUUID")]
416    pub snapshot_uuid: Uuid,
417    pub status: SnapshotStatus,
418    pub total_size: u64,
419    #[serde(rename = "virtualVolumeID")]
420    pub virtual_volume_id: Uuid,
421    #[serde(rename = "volumeID")]
422    pub volume_id: u64,
423}
424
425#[derive(Debug, Deserialize, IntoPoint)]
426#[serde(rename_all = "camelCase")]
427pub struct SnapshotReplication {
428    pub state: String,
429    pub state_details: String,
430}
431
432#[derive(Debug, Deserialize, IntoPoint)]
433#[serde(rename_all = "camelCase")]
434pub struct StorageContainer {
435    #[serde(rename = "accountID")]
436    pub account_id: u64,
437    pub initiator_secret: String,
438    pub name: String,
439    pub protocol_endpoint_type: String,
440    pub status: StorageContainerStatus,
441    #[serde(rename = "storageContainerID")]
442    pub storage_container_id: Uuid,
443    pub target_secret: String,
444    pub virtual_volumes: Vec<Uuid>,
445}
446
447#[derive(Debug, Deserialize)]
448#[serde(rename_all = "camelCase")]
449pub enum SnapshotStatus {
450    Unknown,
451    Preparing,
452    RemoteSyncing,
453    Done,
454    Active,
455    Cloning,
456}
457
458#[derive(Debug, Deserialize)]
459#[serde(rename_all = "camelCase")]
460pub enum StorageContainerStatus {
461    Active,
462    Locked,
463}
464
465#[derive(Debug, Deserialize, IntoPoint)]
466pub struct VolumesAttributesImageInfo {
467    pub image_created_at: String,
468    pub image_id: String,
469    pub image_name: String,
470    pub image_updated_at: String,
471}
472
473/*
474#[derive(Debug, Deserialize)]
475pub struct VolumesAttribute {
476    pub cloned_count: Option<i64>,
477    pub image_info: VolumesAttributesImageInfo,
478}
479*/
480
481#[derive(Debug, Deserialize, IntoPoint)]
482pub struct VolumesQosCurve {
483    #[serde(rename = "1048576")]
484    pub one_mb: i64,
485    #[serde(rename = "131072")]
486    pub onehundred_twentyeight_kb: i64,
487    #[serde(rename = "16384")]
488    pub sixteen_kb: i64,
489    #[serde(rename = "262144")]
490    pub twohundred_fiftysix_kb: i64,
491    #[serde(rename = "32768")]
492    pub thirtytwo_kb: i64,
493    #[serde(rename = "4096")]
494    pub four_kb: i64,
495    #[serde(rename = "524288")]
496    pub fivehundred_kb: i64,
497    #[serde(rename = "65536")]
498    pub sixtyfive_kb: i64,
499    #[serde(rename = "8192")]
500    pub eight_kb: i64,
501}
502
503#[derive(Debug, Deserialize, IntoPoint)]
504pub struct VolumesQos {
505    #[serde(rename = "burstIOPS")]
506    pub burst_iops: i64,
507    #[serde(rename = "burstTime")]
508    pub burst_time: i64,
509    pub curve: VolumesQosCurve,
510    #[serde(rename = "maxIOPS")]
511    pub max_iops: i64,
512    #[serde(rename = "minIOPS")]
513    pub min_iops: i64,
514}
515
516#[derive(Debug, Deserialize)]
517#[serde(rename_all = "camelCase")]
518pub enum VolumeAccess {
519    ReadOnly,
520    ReadWrite,
521    Locked,
522    ReplicationTarget,
523}
524
525#[derive(Debug, Deserialize)]
526#[serde(rename_all = "camelCase")]
527pub enum VolumeStatus {
528    Cloning,
529    Waiting,
530    Ready,
531}
532
533#[derive(Debug, Deserialize, IntoPoint)]
534pub struct VolumePair {
535    #[serde(rename = "clusterPairID")]
536    pub cluster_pair_id: i64,
537    pub remote_replication: RemoteReplication,
538    #[serde(rename = "remoteSliceID")]
539    pub remote_slice_id: u64,
540    #[serde(rename = "remoteVolumeID")]
541    pub remote_volume_id: u64,
542    pub remote_volume_name: String,
543    #[serde(rename = "volumePairUUID")]
544    pub volume_pair_uuid: String,
545}
546
547#[derive(Debug, Deserialize, IntoPoint)]
548#[serde(rename_all = "camelCase")]
549pub struct VirtualNetwork {
550    pub address_blocks: Vec<AddressBlock>,
551    pub attributes: HashMap<String, String>,
552    pub name: String,
553    pub netmask: String,
554    pub svip: String,
555    pub gateway: String,
556    #[serde(rename = "virtualNetworkID")]
557    pub virtual_network_id: u64,
558    pub virtual_network_tag: u64,
559}
560
561#[derive(Debug, Deserialize, IntoPoint)]
562#[serde(rename_all = "camelCase")]
563pub struct VirtualVolume {
564    pub bindings: Vec<Uuid>,
565    pub children: Vec<Uuid>,
566    pub descendants: Vec<Uuid>,
567    pub metadata: HashMap<String, String>,
568    #[serde(rename = "parentVirtualVolumeID")]
569    pub parent_virtual_volume_id: Uuid,
570    #[serde(rename = "snapshotID")]
571    pub snapshot_id: u64,
572    pub snapshot_info: Snapshot,
573    pub status: VolumeStatus,
574    pub storage_container: StorageContainer,
575    #[serde(rename = "virtualVolumeID")]
576    pub virtual_volume_id: Uuid,
577    pub virtual_volume_type: String,
578    #[serde(rename = "volumeID")]
579    pub volume_id: u64,
580    pub volume_info: Option<Volume>,
581}
582
583#[derive(Debug, Deserialize, IntoPoint)]
584#[serde(rename_all = "camelCase")]
585pub struct Volume {
586    pub access: VolumeAccess,
587    #[serde(rename = "accountID")]
588    pub account_id: u64,
589    pub attributes: HashMap<String, serde_json::Value>,
590    pub block_size: u64,
591    pub create_time: String,
592    pub delete_time: String,
593    pub enable512e: bool,
594    pub enable_snap_mirror_replication: bool,
595    pub iqn: String,
596    pub last_access_time: Option<String>,
597    #[serde(rename = "lastAccessTimeIO")]
598    pub last_access_time_io: Option<String>,
599    pub name: String,
600    pub purge_time: String,
601    pub qos: VolumesQos,
602    #[serde(rename = "qosPolicyID")]
603    pub qos_policy_id: Option<i64>,
604    #[serde(rename = "scsiEUIDeviceID")]
605    pub scsi_eui_device_id: String,
606    #[serde(rename = "scsiNAADeviceID")]
607    pub scsi_naa_device_id: String,
608    pub slice_count: i64,
609    pub status: String,
610    pub total_size: i64,
611    #[serde(rename = "virtualVolumeID")]
612    pub virtual_volume_id: Option<Uuid>,
613    pub volume_access_groups: Vec<u64>,
614    #[serde(rename = "volumeConsistencyGroupUUID")]
615    pub volume_consistency_group_uuid: Uuid,
616    #[serde(rename = "volumeID")]
617    pub volume_id: u64,
618    pub volume_pairs: Vec<VolumePair>,
619    #[serde(rename = "volumeUUID")]
620    pub volume_uuid: Uuid,
621}
622
623#[derive(Debug, Deserialize, IntoPoint)]
624#[serde(rename_all = "camelCase")]
625pub struct VolumeStats {
626    #[serde(rename = "accountID")]
627    pub account_id: u64,
628    #[serde(rename = "actualIOPS")]
629    pub actual_iops: u64,
630    pub async_delay: Option<String>,
631    #[serde(rename = "averageIOPSize")]
632    pub average_iop_size: u64,
633    #[serde(rename = "burstIOPSCredit")]
634    pub burst_iops_credit: u64,
635    pub client_queue_depth: u64,
636    pub cluster_utilization: Option<f64>,
637    pub desired_metadata_hosts: Option<serde_json::Value>,
638    pub latency_u_sec: u64,
639    pub metadata_hosts: MetadataHosts,
640    pub non_zero_blocks: u64,
641    pub read_bytes: u64,
642    pub read_bytes_last_sample: u64,
643    pub read_latency_u_sec: u64,
644    pub read_latency_u_sec_total: u64,
645    pub read_ops: u64,
646    pub read_ops_last_sample: u64,
647    pub sample_period_m_sec: u64,
648    pub throttle: f64,
649    pub timestamp: String,
650    pub unaligned_reads: u64,
651    pub unaligned_writes: u64,
652    pub volume_access_groups: Vec<u64>,
653    #[serde(rename = "volumeID")]
654    pub volume_id: u64,
655    pub volume_size: u64,
656    pub volume_utilization: f64,
657    pub write_bytes: u64,
658    pub write_bytes_last_sample: u64,
659    pub write_latency_u_sec: u64,
660    pub write_latency_u_sec_total: u64,
661    pub write_ops: u64,
662    pub write_ops_last_sample: u64,
663    pub zero_blocks: u64,
664}
665
666#[derive(Debug, Deserialize)]
667#[serde(rename_all = "camelCase")]
668pub struct VolumeStatsResult {
669    pub volume_stats: VolumeStats,
670}
671
672impl IntoPoint for VolumeStatsResult {
673    fn into_point(&self, name: Option<&str>, is_time_series: bool) -> Vec<TsPoint> {
674        self.volume_stats.into_point(name, is_time_series)
675    }
676}
677
678#[derive(Debug, Deserialize)]
679#[serde(rename_all = "camelCase")]
680struct DriveHardwareResult {
681    nodes: Vec<HardwareNode>,
682}
683
684impl IntoPoint for DriveHardwareResult {
685    fn into_point(&self, name: Option<&str>, is_time_series: bool) -> Vec<TsPoint> {
686        self.nodes
687            .iter()
688            .flat_map(|n| n.into_point(name, is_time_series))
689            .collect::<Vec<TsPoint>>()
690    }
691}
692
693#[test]
694fn test_get_drive_hardware() {
695    use std::fs::File;
696    use std::io::Read;
697
698    let mut f = File::open("tests/solidfire/list_drive_hardware.json").unwrap();
699    let mut buff = String::new();
700    f.read_to_string(&mut buff).unwrap();
701
702    let r: JsonResult<HardwareNodes> = serde_json::from_str(&buff).unwrap();
703    println!(
704        "JsonResult: {:?}",
705        r.result.into_point(Some("solidfire_drive_hardware"), true)
706    );
707}
708
709#[derive(Debug, Deserialize)]
710struct HardwareNodes {
711    nodes: Vec<HardwareNode>,
712}
713
714impl IntoPoint for HardwareNodes {
715    fn into_point(&self, name: Option<&str>, is_time_series: bool) -> Vec<TsPoint> {
716        self.nodes
717            .iter()
718            .flat_map(|n| n.into_point(name, is_time_series))
719            .collect::<Vec<TsPoint>>()
720    }
721}
722
723#[derive(Debug, Deserialize)]
724#[serde(rename_all = "camelCase")]
725struct HardwareNode {
726    #[serde(rename = "nodeID")]
727    node_id: u64,
728    result: DriveHardware,
729}
730
731impl IntoPoint for HardwareNode {
732    fn into_point(&self, name: Option<&str>, is_time_series: bool) -> Vec<TsPoint> {
733        self.result.into_point(name, is_time_series)
734    }
735}
736
737#[derive(Debug, Deserialize)]
738#[serde(rename_all = "camelCase")]
739struct DriveHardware {
740    drive_hardware: Vec<HardwareDrive>,
741}
742
743impl IntoPoint for DriveHardware {
744    fn into_point(&self, name: Option<&str>, is_time_series: bool) -> Vec<TsPoint> {
745        self.drive_hardware
746            .iter()
747            .flat_map(|n| n.into_point(name, is_time_series))
748            .collect::<Vec<TsPoint>>()
749    }
750}
751
752#[derive(Debug, Deserialize, IntoPoint)]
753#[serde(rename_all = "camelCase")]
754struct HardwareDrive {
755    canonical_name: String,
756    connected: bool,
757    dev: u64,
758    dev_path: String,
759    drive_type: String,
760    life_remaining_percent: u64,
761    lifetime_read_bytes: u64,
762    lifetime_write_bytes: u64,
763    name: String,
764    path: String,
765    path_link: String,
766    power_on_hours: u64,
767    product: String,
768    reallocated_sectors: u64,
769    reserve_capacity_percent: u64,
770    scsi_compat_id: String,
771    scsi_state: String,
772    security_at_maximum: bool,
773    security_enabled: bool,
774    security_frozen: bool,
775    security_locked: bool,
776    security_supported: bool,
777    serial: String,
778    size: u64,
779    slot: u64,
780    uncorrectable_errors: u64,
781    #[serde(rename = "uuid")]
782    hardware_uuid: Uuid,
783    vendor: String,
784    version: String,
785}
786
787#[derive(Debug, Deserialize)]
788pub struct Volumes {
789    pub volumes: Vec<Volume>,
790}
791
792#[derive(Debug, Deserialize)]
793pub struct JsonResult<T> {
794    pub id: Option<String>,
795    pub result: T,
796}
797
798#[test]
799fn test_get_cluster_capacity() {
800    use std::fs::File;
801    use std::io::Read;
802
803    let mut f = File::open("tests/solidfire/get_cluster_capacity.json").unwrap();
804    let mut buff = String::new();
805    f.read_to_string(&mut buff).unwrap();
806
807    let r: JsonResult<ClusterCapacityResult> = serde_json::from_str(&buff).unwrap();
808    println!("JsonResult: {:?}", r);
809}
810
811#[test]
812fn test_get_cluster_fullness() {
813    use std::fs::File;
814    use std::io::Read;
815
816    let mut f = File::open("tests/solidfire/get_cluster_full_threshold.json").unwrap();
817    let mut buff = String::new();
818    f.read_to_string(&mut buff).unwrap();
819
820    let r: JsonResult<ClusterFullThreshold> = serde_json::from_str(&buff).unwrap();
821    println!("JsonResult: {:?}", r);
822}
823
824#[test]
825fn test_get_cluster_info() {
826    use std::fs::File;
827    use std::io::Read;
828
829    let mut f = File::open("tests/solidfire/get_cluster_info.json").unwrap();
830    let mut buff = String::new();
831    f.read_to_string(&mut buff).unwrap();
832
833    let r: JsonResult<ClusterInfoResult> = serde_json::from_str(&buff).unwrap();
834    println!("JsonResult: {:?}", r);
835}
836
837#[test]
838fn test_get_cluster_stats() {
839    use std::fs::File;
840    use std::io::Read;
841
842    let mut f = File::open("tests/solidfire/get_cluster_stats.json").unwrap();
843    let mut buff = String::new();
844    f.read_to_string(&mut buff).unwrap();
845
846    let r: JsonResult<ClusterStatsResult> = serde_json::from_str(&buff).unwrap();
847    println!("JsonResult: {:?}", r);
848}
849
850#[test]
851fn test_get_volume_stats() {
852    use std::fs::File;
853    use std::io::Read;
854
855    let mut f = File::open("tests/solidfire/get_volume_stats.json").unwrap();
856    let mut buff = String::new();
857    f.read_to_string(&mut buff).unwrap();
858
859    let r: JsonResult<VolumeStatsResult> = serde_json::from_str(&buff).unwrap();
860    println!("JsonResult: {:?}", r);
861}
862
863#[test]
864fn test_list_sf_volumes() {
865    use std::fs::File;
866    use std::io::Read;
867
868    let mut f = File::open("tests/solidfire/list_volumes.json").unwrap();
869    let mut buff = String::new();
870    f.read_to_string(&mut buff).unwrap();
871
872    let r: JsonResult<Volumes> = serde_json::from_str(&buff).unwrap();
873    println!("JsonResult: {:?}", r);
874}
875
876#[test]
877fn test_list_sf_nodes() {
878    use std::fs::File;
879    use std::io::Read;
880
881    let mut f = File::open("tests/solidfire/list_active_nodes.json").unwrap();
882    let mut buff = String::new();
883    f.read_to_string(&mut buff).unwrap();
884
885    let r: JsonResult<Nodes> = serde_json::from_str(&buff).unwrap();
886    println!("JsonResult: {:?}", r);
887}
888
889impl Solidfire {
890    // Call out to solidfire and return the result as a json deserialized struct
891    pub fn get<T>(
892        &self,
893        method: &str,
894        params: Option<HashMap<String, String>>,
895        force: bool,
896    ) -> MetricsResult<T>
897    where
898        T: DeserializeOwned + Debug,
899    {
900        let mut url = format!(
901            "https://{}/json-rpc/8.4?method={}",
902            self.config.endpoint, method
903        );
904        if force {
905            url.push_str("&force=true");
906        }
907        if let Some(p) = params {
908            url.push_str(
909                &p.into_iter()
910                    .map(|(k, v)| format!("&{}={}", k, v))
911                    .collect::<Vec<String>>()
912                    .join(""),
913            );
914        }
915        let j: T = crate::get(
916            &self.client,
917            &url,
918            &self.config.user,
919            Some(&self.config.password),
920        )?;
921
922        Ok(j)
923    }
924
925    pub fn get_drive_hardware_info(&self, t: DateTime<Utc>) -> MetricsResult<Vec<TsPoint>> {
926        debug!("get_hardware_info");
927        let info = self.get::<JsonResult<HardwareNodes>>("ListDriveHardware", None, true)?;
928        Ok(info
929            .result
930            .into_point(Some("solidfire_drive_hardware"), true)
931            .into_iter()
932            .map(|mut p| {
933                p.timestamp = Some(t);
934                p
935            })
936            .collect::<Vec<TsPoint>>())
937    }
938
939    pub fn get_cluster_capacity(&self, t: DateTime<Utc>) -> MetricsResult<Vec<TsPoint>> {
940        debug!("get_cluster_capacity");
941        let info =
942            self.get::<JsonResult<ClusterCapacityResult>>("GetClusterCapacity", None, false)?;
943        Ok(info
944            .result
945            .into_point(Some("solidfire_cluster_capacity"), true)
946            .into_iter()
947            .map(|mut p| {
948                p.timestamp = Some(t);
949                p
950            })
951            .collect::<Vec<TsPoint>>())
952    }
953
954    pub fn get_cluster_fullness(&self, t: DateTime<Utc>) -> MetricsResult<Vec<TsPoint>> {
955        debug!("get_cluster_fullness");
956        let info =
957            self.get::<JsonResult<ClusterFullThreshold>>("GetClusterFullThreshold", None, false)?;
958        Ok(info
959            .result
960            .into_point(Some("solidfire_cluster_full_threshold"), true)
961            .into_iter()
962            .map(|mut p| {
963                p.timestamp = Some(t);
964                p
965            })
966            .collect::<Vec<TsPoint>>())
967    }
968
969    pub fn get_cluster_info(&self) -> MetricsResult<ClusterInfoResult> {
970        debug!("get_cluster_info");
971        let info = self.get::<JsonResult<ClusterInfoResult>>("GetClusterInfo", None, false)?;
972        Ok(info.result)
973    }
974
975    pub fn get_cluster_stats(&self, t: DateTime<Utc>) -> MetricsResult<Vec<TsPoint>> {
976        debug!("get_cluster_stats");
977        let info = self.get::<JsonResult<ClusterStatsResult>>("GetClusterStats", None, false)?;
978        Ok(info
979            .result
980            .into_point(Some("solidfire_cluster_stats"), true)
981            .into_iter()
982            .map(|mut p| {
983                p.timestamp = Some(t);
984                p
985            })
986            .collect::<Vec<TsPoint>>())
987    }
988
989    pub fn get_volume_stats(
990        &self,
991        volume_id: u64,
992        t: DateTime<Utc>,
993    ) -> MetricsResult<Vec<TsPoint>> {
994        let mut params = HashMap::new();
995        params.insert("volumeID".to_string(), volume_id.to_string());
996
997        debug!("get_volume_stats");
998        let info =
999            self.get::<JsonResult<VolumeStatsResult>>("GetVolumeStats", Some(params), false)?;
1000        Ok(info
1001            .result
1002            .into_point(Some("solidfire_volume_stats"), true)
1003            .into_iter()
1004            .map(|mut p| {
1005                p.timestamp = Some(t);
1006                p
1007            })
1008            .collect::<Vec<TsPoint>>())
1009    }
1010
1011    //pub fn get_node_stats() -> MetricsResult<Vec<TsPoint>> {
1012    //
1013    //}
1014
1015    pub fn list_volumes(&self) -> MetricsResult<Volumes> {
1016        debug!("list_volumes");
1017        let info = self.get::<JsonResult<Volumes>>("ListVolumes", None, false)?;
1018        Ok(info.result)
1019    }
1020
1021    pub fn list_volume_ids(&self) -> MetricsResult<Vec<u64>> {
1022        debug!("list_volume_ids");
1023        let info = self.get::<JsonResult<Volumes>>("ListVolumes", None, false)?;
1024        Ok(info
1025            .result
1026            .volumes
1027            .into_iter()
1028            .map(|v| v.volume_id)
1029            .collect::<Vec<u64>>())
1030    }
1031}