lsm/
client.rs

1// Copyright (C) 2017-2018 Red Hat, Inc.
2//
3// Permission is hereby granted, free of charge, to any
4// person obtaining a copy of this software and associated
5// documentation files (the "Software"), to deal in the
6// Software without restriction, including without
7// limitation the rights to use, copy, modify, merge,
8// publish, distribute, sublicense, and/or sell copies of
9// the Software, and to permit persons to whom the Software
10// is furnished to do so, subject to the following
11// conditions:
12//
13// The above copyright notice and this permission notice
14// shall be included in all copies or substantial portions
15// of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25// DEALINGS IN THE SOFTWARE.
26//
27// Author: Gris Ge <fge@redhat.com>
28
29use serde_json;
30use serde_json::{Map, Value};
31use std::fs::read_dir;
32use std::thread::sleep;
33use std::time::Duration;
34use url;
35
36use super::data::*;
37use super::error::*;
38use super::ipc::{uds_path, TransPort};
39use super::misc::verify_init_id_str;
40
41use std::fs;
42use std::os::linux::fs::MetadataExt;
43
44const DEFAULT_TIMEOUT: u32 = 30_000;
45const JOB_RETRY_INTERVAL: u64 = 1;
46
47/// Represent the connection to plugin.
48pub struct Client {
49    tp: TransPort,
50    plugin_name: String,
51}
52
53#[allow(dead_code)]
54#[derive(Deserialize, Debug)]
55struct Job {
56    status: u32,
57    percent: u8,
58    data: Option<Value>,
59}
60
61const JOB_STATUS_INPROGRESS: u32 = 1;
62const JOB_STATUS_COMPLETE: u32 = 2;
63const JOB_STATUS_ERROR: u32 = 3;
64const VOLUME_THINP_YES: u32 = 1;
65const VOLUME_THINP_NO: u32 = 2;
66const VOLUME_THINP_DEFAULT: u32 = 3;
67const POOL_MEMBER_TYPE_DISK: u32 = 2;
68const POOL_MEMBER_TYPE_POOL: u32 = 3;
69
70const WRITE_CACHE_POLICY_WRITE_BACK: u8 = 2;
71const WRITE_CACHE_POLICY_AUTO: u8 = 3;
72const WRITE_CACHE_POLICY_WRITE_THROUGH: u8 = 4;
73
74const WRITE_CACHE_STATUS_WRITE_BACK: u8 = 2;
75const WRITE_CACHE_STATUS_WRITE_THROUGH: u8 = 3;
76
77const READ_CACHE_POLICY_ENABLED: u8 = 2;
78const READ_CACHE_POLICY_DISABLED: u8 = 3;
79
80const READ_CACHE_STATUS_ENABLED: u8 = 2;
81const READ_CACHE_STATUS_DISABLED: u8 = 3;
82
83const PHYSICAL_DISK_CACHE_ENABLED: u8 = 2;
84const PHYSICAL_DISK_CACHE_DISABLED: u8 = 3;
85const PHYSICAL_DISK_CACHE_USE_DISK_SETTING: u8 = 4;
86
87trait OkOrPlugBug<T> {
88    fn ok_or_plugin_bug(self, val: &Value) -> Result<T>;
89}
90
91impl<T> OkOrPlugBug<T> for Option<T> {
92    fn ok_or_plugin_bug(self, val: &Value) -> Result<T> {
93        match self {
94            Some(i) => Ok(i),
95            None => Err(LsmError::PluginBug(format!(
96                "Plugin return unexpected data: {:?}",
97                val
98            ))),
99        }
100    }
101}
102
103/// Represent a plugin information
104#[derive(Debug)]
105pub struct PluginInfo {
106    /// Plugin version string.
107    pub version: String,
108    /// Plugin description.
109    pub description: String,
110    /// Plugin name.
111    pub name: String,
112}
113
114/// Query all available plugins from libStorageMgmt daemon (lsmd).
115///
116/// # Errors
117///
118///  * [`LsmError::DaemonNotRunning`][1]
119///
120/// [1]: enum.LsmError.html#variant.DaemonNotRunning
121pub fn available_plugins() -> Result<Vec<PluginInfo>> {
122    let mut ret = Vec::new();
123    let uds_path = uds_path();
124    match read_dir(&uds_path) {
125        Err(_) => {
126            return Err(LsmError::DaemonNotRunning(format!(
127                "LibStorageMgmt daemon is not running for \
128                 socket folder: '{}'",
129                uds_path
130            )))
131        }
132        Ok(paths) => {
133            for path in paths {
134                match path {
135                    // Got error when iterate, it might happen when
136                    // daemon is starting.
137                    //
138                    Err(_) => continue,
139                    Ok(dir_entry) => {
140                        let plugin_name = match dir_entry.file_name().into_string() {
141                            Ok(i) => i,
142                            Err(_) => continue,
143                        };
144                        let plugin_ipc_path = get_plugin_ipc_path(&plugin_name);
145
146                        // Make sure the file refers to a unix domain socket as the library
147                        // added a lock file to prevent concurrent access to same unix domain
148                        // socket directory for the lsm daemon.
149                        match fs::metadata(&plugin_ipc_path) {
150                            Ok(meta) => {
151                                if meta.st_mode() & 0o140000 != 0o140000 {
152                                    continue;
153                                }
154                            }
155                            Err(_) => continue,
156                        }
157
158                        // We cannot use self.plugin_info() here, as we need
159                        // to bypass the plugin_register() and
160                        // plugin_unregister()
161                        //
162                        let mut tp = TransPort::new(&plugin_ipc_path)?;
163                        let val = tp.invoke("plugin_info", None)?;
164                        let data: Vec<String> = serde_json::from_value(val.clone())?;
165                        let desc = data.first().ok_or_plugin_bug(&val)?;
166                        let version = data.get(1).ok_or_plugin_bug(&val)?;
167                        ret.push(PluginInfo {
168                            version: version.to_string(),
169                            description: desc.to_string(),
170                            name: plugin_name,
171                        });
172                    }
173                };
174            }
175        }
176    };
177
178    Ok(ret)
179}
180
181fn get_plugin_ipc_path(plugin_name: &str) -> String {
182    format!("{}/{}", uds_path(), plugin_name)
183}
184
185impl Client {
186    /// Create a connection to plugin.
187    /// Please refer to [libstoragemgmt user guide][1] for how to choose the
188    /// URI and password.
189    ///
190    /// The `timeout` argument is in milliseconds.
191    ///
192    /// [1]: https://libstorage.github.io/libstoragemgmt-doc/doc/user_guide.html
193    pub fn new(uri: &str, password: Option<&str>, timeout: Option<u32>) -> Result<Client> {
194        let p = match url::Url::parse(uri) {
195            Ok(p) => p,
196            Err(e) => {
197                return Err(LsmError::InvalidArgument(format!(
198                    "Failed to parse URI: {}",
199                    e
200                )))
201            }
202        };
203        let plugin_name = p.scheme().to_string();
204        let plugin_ipc_path = get_plugin_ipc_path(&plugin_name);
205        let mut tp = TransPort::new(&plugin_ipc_path)?;
206        let mut args = Map::new();
207        let timeout = timeout.unwrap_or(DEFAULT_TIMEOUT);
208        args.insert("password".to_string(), serde_json::to_value(password)?);
209        args.insert("uri".to_string(), serde_json::to_value(uri)?);
210        args.insert("timeout".to_string(), serde_json::to_value(timeout)?);
211        tp.invoke("plugin_register", Some(args))?;
212
213        Ok(Client { tp, plugin_name })
214    }
215
216    /// Gets a list of systems on this connection.
217    pub fn systems(&mut self) -> Result<Vec<System>> {
218        Ok(serde_json::from_value(self.tp.invoke("systems", None)?)?)
219    }
220
221    /// Gets a list of volumes on this connection.
222    pub fn volumes(&mut self) -> Result<Vec<Volume>> {
223        Ok(serde_json::from_value(self.tp.invoke("volumes", None)?)?)
224    }
225
226    /// Gets a list of pools on this connection.
227    pub fn pools(&mut self) -> Result<Vec<Pool>> {
228        Ok(serde_json::from_value(self.tp.invoke("pools", None)?)?)
229    }
230
231    /// Gets a list of disks on this connection.
232    pub fn disks(&mut self) -> Result<Vec<Disk>> {
233        Ok(serde_json::from_value(self.tp.invoke("disks", None)?)?)
234    }
235
236    /// Gets a list of file systems on this connection.
237    pub fn fs(&mut self) -> Result<Vec<FileSystem>> {
238        Ok(serde_json::from_value(self.tp.invoke("fs", None)?)?)
239    }
240
241    /// Gets a list of NFS exports on this connection.
242    pub fn nfs_exports(&mut self) -> Result<Vec<NfsExport>> {
243        Ok(serde_json::from_value(self.tp.invoke("exports", None)?)?)
244    }
245
246    /// Gets a list of access group on this connection.
247    pub fn access_groups(&mut self) -> Result<Vec<AccessGroup>> {
248        Ok(serde_json::from_value(
249            self.tp.invoke("access_groups", None)?,
250        )?)
251    }
252
253    /// Gets a list of target ports on this connection.
254    pub fn target_ports(&mut self) -> Result<Vec<TargetPort>> {
255        Ok(serde_json::from_value(
256            self.tp.invoke("target_ports", None)?,
257        )?)
258    }
259
260    /// Gets a list of batteries on this connection.
261    pub fn batteries(&mut self) -> Result<Vec<Battery>> {
262        Ok(serde_json::from_value(self.tp.invoke("batteries", None)?)?)
263    }
264
265    fn _job_free(&mut self, job_id: &str) -> Result<()> {
266        let mut args = Map::new();
267        args.insert("job_id".to_string(), serde_json::to_value(job_id)?);
268        self.tp.invoke("job_free", Some(args))?;
269        Ok(())
270    }
271
272    fn _wait_job(&mut self, job_id: &str) -> Result<Value> {
273        loop {
274            let mut args = Map::new();
275            args.insert("job_id".to_string(), serde_json::to_value(job_id)?);
276            let j: Job = serde_json::from_value(self.tp.invoke("job_status", Some(args))?)?;
277
278            match j.status {
279                JOB_STATUS_INPROGRESS => {
280                    sleep(Duration::new(JOB_RETRY_INTERVAL, 0));
281                    continue;
282                }
283                JOB_STATUS_COMPLETE => match j.data {
284                    Some(v) => {
285                        self._job_free(job_id)?;
286                        return Ok(v);
287                    }
288                    None => break,
289                },
290                JOB_STATUS_ERROR =>
291                // The invoke command should already got error detail
292                // and returned. If not, got buggy plugin.
293                {
294                    return Err(LsmError::PluginBug(
295                        "Got no error detail for failed job".to_string(),
296                    ))
297                }
298                _ => {
299                    return Err(LsmError::PluginBug(format!(
300                        "Got invalid job status {}",
301                        j.status
302                    )))
303                }
304            };
305        }
306        Ok(Value::Null)
307    }
308
309    fn wait_job_none(&mut self, job_id: &str) -> Result<()> {
310        self._wait_job(job_id)?;
311        Ok(())
312    }
313
314    fn wait_job_volume(&mut self, job_id: &str) -> Result<Volume> {
315        match self._wait_job(job_id) {
316            Ok(j) => {
317                if j.is_null() {
318                    Err(LsmError::PluginBug(
319                        "Expecting a volume, but got None".to_string(),
320                    ))
321                } else {
322                    let v: Volume = serde_json::from_value(j)?;
323                    Ok(v)
324                }
325            }
326            Err(e) => Err(e),
327        }
328    }
329
330    fn wait_job_fs(&mut self, job_id: &str) -> Result<FileSystem> {
331        match self._wait_job(job_id) {
332            Ok(j) => {
333                if j.is_null() {
334                    Err(LsmError::PluginBug(
335                        "Expecting a file system, but got None".to_string(),
336                    ))
337                } else {
338                    let f: FileSystem = serde_json::from_value(j)?;
339                    Ok(f)
340                }
341            }
342            Err(e) => Err(e),
343        }
344    }
345
346    fn wait_job_fs_snap(&mut self, job_id: &str) -> Result<FileSystemSnapShot> {
347        match self._wait_job(job_id) {
348            Ok(j) => {
349                if j.is_null() {
350                    Err(LsmError::PluginBug(
351                        "Expecting a file system snapshot, but got None".to_string(),
352                    ))
353                } else {
354                    let f: FileSystemSnapShot = serde_json::from_value(j)?;
355                    Ok(f)
356                }
357            }
358            Err(e) => Err(e),
359        }
360    }
361
362    /// Create new volume.
363    ///
364    ///  * `pool` -- The pool where new volume should allocated from.
365    ///  * `name` -- The name of new volume. It might be altered or
366    ///    ignored.
367    ///  * `size_bytes` -- Size in bytes of new volume. You may use function
368    ///    [`size_human_2_size_bytes()`][1] to convert string like '1.1 GiB'
369    ///    to integer size bytes.
370    ///  * `thinp` -- Whether to create thin provisioning volume.
371    ///    Check [VolumeCreateArgThinP][2]
372    ///
373    /// [1]: fn.size_human_2_size_bytes.html
374    /// [2]: enum.VolumeCreateArgThinP.html
375    pub fn volume_create(
376        &mut self,
377        pool: &Pool,
378        name: &str,
379        size_bytes: u64,
380        thinp: &VolumeCreateArgThinP,
381    ) -> Result<Volume> {
382        let mut args = Map::new();
383        let thinp_val = match *thinp {
384            VolumeCreateArgThinP::Full => serde_json::to_value(VOLUME_THINP_YES)?,
385            VolumeCreateArgThinP::Thin => serde_json::to_value(VOLUME_THINP_NO)?,
386            VolumeCreateArgThinP::Default => serde_json::to_value(VOLUME_THINP_DEFAULT)?,
387        };
388        args.insert("provisioning".to_string(), thinp_val);
389        args.insert("size_bytes".to_string(), serde_json::to_value(size_bytes)?);
390        args.insert("volume_name".to_string(), serde_json::to_value(name)?);
391        args.insert("pool".to_string(), serde_json::to_value(pool)?);
392
393        let ret = self.tp.invoke("volume_create", Some(args))?;
394        self.get_vol_from_async(&ret)
395    }
396
397    /// Delete a volume
398    ///
399    ///
400    /// # Errors
401    ///
402    ///  * [`LsmError::VolHasChildDep`][1] volume has child dependency. e.g.
403    ///    specified volume is a replication source. Please use
404    ///    [`Client::vol_child_dep_rm()`] to eliminate child dependency.
405    ///
406    /// [1]: enum.LsmError.html#variant.VolHasChildDep
407    pub fn volume_delete(&mut self, vol: &Volume) -> Result<()> {
408        let mut args = Map::new();
409        args.insert("volume".to_string(), serde_json::to_value(vol)?);
410        let ret = self.tp.invoke("volume_delete", Some(args))?;
411        self.wait_if_async(&ret)
412    }
413
414    /// Set connection timeout value in milliseconds.
415    pub fn time_out_set(&mut self, ms: u32) -> Result<()> {
416        let mut args = Map::new();
417        args.insert("ms".to_string(), serde_json::to_value(ms)?);
418        self.tp.invoke("time_out_set", Some(args))?;
419        Ok(())
420    }
421
422    /// Get connection timeout value.
423    pub fn time_out_get(&mut self) -> Result<u32> {
424        Ok(serde_json::from_value(
425            self.tp.invoke("time_out_get", None)?,
426        )?)
427    }
428
429    /// Get system's capabilities.
430    ///
431    /// Capability is used to indicate whether certain functionality is
432    /// supported by specified storage system. Please check desired function
433    /// for required capability. To verify capability is supported, use
434    /// [`Capabilities::is_supported()`][1]. If the functionality is not
435    /// listed in the enumerated [`Capability`][2] type then that functionality
436    /// is mandatory and required to exist.
437    ///
438    /// [1]: struct.Capabilities.html#method.is_supported
439    /// [2]: enum.capability.html
440    pub fn capabilities(&mut self, sys: &System) -> Result<Capabilities> {
441        let mut args = Map::new();
442        args.insert("system".to_string(), serde_json::to_value(sys)?);
443        Ok(serde_json::from_value(
444            self.tp.invoke("capabilities", Some(args))?,
445        )?)
446    }
447
448    /// Get plugin information.
449    pub fn plugin_info(&mut self) -> Result<PluginInfo> {
450        let val = self.tp.invoke("plugin_info", None)?;
451        let data: Vec<String> = serde_json::from_value(val.clone())?;
452        let desc = data.first().ok_or_plugin_bug(&val)?;
453        let version = data.get(1).ok_or_plugin_bug(&val)?;
454        Ok(PluginInfo {
455            version: version.to_string(),
456            description: desc.to_string(),
457            name: self.plugin_name.clone(),
458        })
459    }
460
461    /// Changes the read cache percentage for the specified system.
462    ///
463    /// # Errors
464    ///
465    ///  * [`LsmError::InvalidArgument`][1]: `read_pct` is larger than 100.
466    ///
467    /// [1]: enum.LsmError.html#variant.InvalidArgument
468    pub fn sys_read_cache_pct_set(&mut self, sys: &System, read_pct: u32) -> Result<()> {
469        if read_pct > 100 {
470            return Err(LsmError::InvalidArgument(
471                "Invalid read_pct, should be in range 0 - 100".to_string(),
472            ));
473        }
474        let mut args = Map::new();
475        args.insert("system".to_string(), serde_json::to_value(sys)?);
476        args.insert("read_pct".to_string(), serde_json::to_value(read_pct)?);
477        Ok(serde_json::from_value(
478            self.tp.invoke("system_read_cache_pct_update", Some(args))?,
479        )?)
480    }
481
482    /// Set(override) iSCSI CHAP authentication.
483    ///
484    ///  * `init_id` -- Initiator ID.
485    ///  * `in_user` -- The inbound authentication username. The inbound
486    ///    authentication means the iSCSI initiator authenticates the iSCSI
487    ///    target using CHAP.
488    ///  * `in_pass` -- The inbound authentication password.
489    ///  * `out_user` -- The outbound authentication username. The outbound
490    ///    authentication means the iSCSI target authenticates the iSCSI
491    ///    initiator using CHAP.
492    ///  * `out_pass` -- The outbound authentication password.
493    pub fn iscsi_chap_auth_set(
494        &mut self,
495        init_id: &str,
496        in_user: Option<&str>,
497        in_pass: Option<&str>,
498        out_user: Option<&str>,
499        out_pass: Option<&str>,
500    ) -> Result<()> {
501        let mut args = Map::new();
502        args.insert("init_id".to_string(), serde_json::to_value(init_id)?);
503        args.insert(
504            "in_user".to_string(),
505            serde_json::to_value(in_user.unwrap_or(""))?,
506        );
507        args.insert(
508            "in_password".to_string(),
509            serde_json::to_value(in_pass.unwrap_or(""))?,
510        );
511        args.insert(
512            "out_user".to_string(),
513            serde_json::to_value(out_user.unwrap_or(""))?,
514        );
515        args.insert(
516            "out_password".to_string(),
517            serde_json::to_value(out_pass.unwrap_or(""))?,
518        );
519        self.tp.invoke("iscsi_chap_auth", Some(args))?;
520        Ok(())
521    }
522
523    /// Resize a volume.
524    ///
525    /// Please check whether pool allows volume resize via
526    /// [`Pool.unsupported_actions`][1].
527    ///
528    /// [1]: struct.Pool.html#structfield.unsupported_actions
529    pub fn volume_resize(&mut self, vol: &Volume, new_size_bytes: u64) -> Result<Volume> {
530        let mut args = Map::new();
531        args.insert("volume".to_string(), serde_json::to_value(vol)?);
532        args.insert(
533            "new_size_bytes".to_string(),
534            serde_json::to_value(new_size_bytes)?,
535        );
536        let ret = self.tp.invoke("volume_resize", Some(args))?;
537        self.get_vol_from_async(&ret)
538    }
539
540    fn wait_if_async(&mut self, ret: &Value) -> Result<()> {
541        if ret.is_null() {
542            return Ok(());
543        }
544        self.wait_job_none(ret.as_str().ok_or_plugin_bug(ret)?)
545    }
546
547    //TODO(Gris Ge): Merge get_fs_from_async() and get_vol_from_asyn().
548    fn get_fs_from_async(&mut self, ret: &Value) -> Result<FileSystem> {
549        let ret_array = ret.as_array().ok_or_plugin_bug(ret)?;
550        if ret_array.len() != 2 {
551            return Err(LsmError::PluginBug(format!(
552                "Plugin return unexpected data: {:?}",
553                ret
554            )));
555        }
556        let job_id = ret_array.first().ok_or_plugin_bug(ret)?;
557        if job_id.is_null() {
558            Ok(serde_json::from_value(
559                ret_array.get(1).ok_or_plugin_bug(ret)?.clone(),
560            )?)
561        } else {
562            self.wait_job_fs(job_id.as_str().ok_or_plugin_bug(ret)?)
563        }
564    }
565
566    fn get_vol_from_async(&mut self, ret: &Value) -> Result<Volume> {
567        let ret_array = ret.as_array().ok_or_plugin_bug(ret)?;
568        if ret_array.len() != 2 {
569            return Err(LsmError::PluginBug(format!(
570                "Plugin return unexpected data: {:?}",
571                ret
572            )));
573        }
574        let job_id = ret_array.first().ok_or_plugin_bug(ret)?;
575        if job_id.is_null() {
576            Ok(serde_json::from_value(
577                ret_array.get(1).ok_or_plugin_bug(ret)?.clone(),
578            )?)
579        } else {
580            self.wait_job_volume(job_id.as_str().ok_or_plugin_bug(ret)?)
581        }
582    }
583
584    fn get_fs_snap_from_asyn(&mut self, ret: &Value) -> Result<FileSystemSnapShot> {
585        let ret_array = ret.as_array().ok_or_plugin_bug(ret)?;
586        if ret_array.len() != 2 {
587            return Err(LsmError::PluginBug(format!(
588                "Plugin return unexpected data: {:?}",
589                ret
590            )));
591        }
592        let job_id = ret_array.first().ok_or_plugin_bug(ret)?;
593        if job_id.is_null() {
594            Ok(serde_json::from_value(
595                ret_array.first().ok_or_plugin_bug(ret)?.clone(),
596            )?)
597        } else {
598            self.wait_job_fs_snap(job_id.as_str().ok_or_plugin_bug(ret)?)
599        }
600    }
601
602    /// Replicate a volume.
603    ///
604    ///  * `pool` -- The pool where new replication target volume should be
605    ///    allocated from. For `None`, will use the same pool of source volume.
606    ///  * `rep_type` -- Replication type.
607    ///  * `src_vol` -- Replication source volume.
608    ///  * `name` -- Name for replication target volume. Might be altered or
609    ///    ignored.
610    pub fn volume_replicate(
611        &mut self,
612        pool: Option<Pool>,
613        rep_type: VolumeReplicateType,
614        src_vol: &Volume,
615        name: &str,
616    ) -> Result<Volume> {
617        let mut args = Map::new();
618        args.insert("pool".to_string(), serde_json::to_value(pool)?);
619        args.insert("volume_src".to_string(), serde_json::to_value(src_vol)?);
620        args.insert(
621            "rep_type".to_string(),
622            serde_json::to_value(rep_type as i32)?,
623        );
624        args.insert("name".to_string(), serde_json::to_value(name)?);
625        let ret = self.tp.invoke("volume_replicate", Some(args))?;
626        self.get_vol_from_async(&ret)
627    }
628
629    /// Block size for the [`Client::volume_replicate_range()`][1].
630    ///
631    /// [1]: #method.volume_replicate_range
632    pub fn volume_rep_range_blk_size(&mut self, sys: &System) -> Result<u32> {
633        let mut args = Map::new();
634        args.insert("system".to_string(), serde_json::to_value(sys)?);
635        Ok(serde_json::from_value(self.tp.invoke(
636            "volume_replicate_range_block_size",
637            Some(args),
638        )?)?)
639    }
640
641    /// Replicates a portion of a volume to a volume.
642    ///
643    /// * `rep_type` -- Replication type.
644    /// * `src_vol` -- Replication source volume.
645    /// * `dst_vol` -- Replication target volume.
646    /// * `ranges` -- Replication block ranges.
647    pub fn volume_replicate_range(
648        &mut self,
649        rep_type: VolumeReplicateType,
650        src_vol: &Volume,
651        dst_vol: &Volume,
652        ranges: &[BlockRange],
653    ) -> Result<()> {
654        let mut args = Map::new();
655        args.insert(
656            "rep_type".to_string(),
657            serde_json::to_value(rep_type as i32)?,
658        );
659        args.insert("ranges".to_string(), serde_json::to_value(ranges)?);
660        args.insert("volume_src".to_string(), serde_json::to_value(src_vol)?);
661        args.insert("volume_dest".to_string(), serde_json::to_value(dst_vol)?);
662        let ret = self.tp.invoke("volume_replicate_range", Some(args))?;
663        self.wait_if_async(&ret)
664    }
665
666    /// Set a Volume to online.
667    ///
668    /// Enable the specified volume when that volume is disabled by
669    /// administrator or via [`Client::volume_disable()`][1]
670    ///
671    /// [1]: #method.volume_disable
672    pub fn volume_enable(&mut self, vol: &Volume) -> Result<()> {
673        let mut args = Map::new();
674        args.insert("volume".to_string(), serde_json::to_value(vol)?);
675        self.tp.invoke("volume_enable", Some(args))?;
676        Ok(())
677    }
678
679    /// Disable the read and write access to the specified volume.
680    pub fn volume_disable(&mut self, vol: &Volume) -> Result<()> {
681        let mut args = Map::new();
682        args.insert("volume".to_string(), serde_json::to_value(vol)?);
683        self.tp.invoke("volume_disable", Some(args))?;
684        Ok(())
685    }
686
687    /// Grant access to a volume for the specified group, also known as LUN
688    /// masking or mapping.
689    ///
690    /// # Errors
691    ///
692    ///  * [`LsmError::EmptyAccessGroup`][1]: Cannot mask volume to empty
693    ///    access group.
694    ///
695    /// [1]: enum.LsmError.html#variant.EmptyAccessGroup
696    pub fn volume_mask(&mut self, vol: &Volume, ag: &AccessGroup) -> Result<()> {
697        let mut args = Map::new();
698        args.insert("volume".to_string(), serde_json::to_value(vol)?);
699        args.insert("access_group".to_string(), serde_json::to_value(ag)?);
700        self.tp.invoke("volume_mask", Some(args))?;
701        Ok(())
702    }
703
704    /// Revokes access to a volume for the specified group
705    pub fn volume_unmask(&mut self, vol: &Volume, ag: &AccessGroup) -> Result<()> {
706        let mut args = Map::new();
707        args.insert("volume".to_string(), serde_json::to_value(vol)?);
708        args.insert("access_group".to_string(), serde_json::to_value(ag)?);
709        self.tp.invoke("volume_unmask", Some(args))?;
710        Ok(())
711    }
712
713    /// Create a access group.
714    ///
715    /// Creates a new access group with one initiator in it. You may expand
716    /// the access group by adding more initiators via
717    /// [`Client::access_group_init_add()`][1]
718    ///
719    /// # Errors
720    ///
721    ///  * [`LsmError::ExistsInitiator`][2]: Specified initiator is used by
722    ///    other access group.
723    ///
724    /// [1]: #method.access_group_init_add
725    /// [2]: enum.LsmError.html#variant.ExistsInitiator
726    pub fn access_group_create(
727        &mut self,
728        name: &str,
729        init_id: &str,
730        init_type: InitiatorType,
731        sys: &System,
732    ) -> Result<AccessGroup> {
733        verify_init_id_str(init_id, init_type)?;
734        let mut args = Map::new();
735        args.insert("name".to_string(), serde_json::to_value(name)?);
736        args.insert("init_id".to_string(), serde_json::to_value(init_id)?);
737        args.insert(
738            "init_type".to_string(),
739            serde_json::to_value(init_type as i32)?,
740        );
741        args.insert("system".to_string(), serde_json::to_value(sys)?);
742        Ok(serde_json::from_value(
743            self.tp.invoke("access_group_create", Some(args))?,
744        )?)
745    }
746
747    /// Delete an access group. Only access group with no volume masked can
748    /// be deleted.
749    ///
750    /// # Errors
751    ///
752    ///  * [`LsmError::IsMasked`][1]: Access group has volume masked to.
753    ///
754    /// [1]: enum.LsmError.html#variant.IsMasked
755    pub fn access_group_delete(&mut self, ag: &AccessGroup) -> Result<()> {
756        let mut args = Map::new();
757        args.insert("access_group".to_string(), serde_json::to_value(ag)?);
758        self.tp.invoke("access_group_delete", Some(args))?;
759        Ok(())
760    }
761
762    /// Add an initiator to the access group.
763    ///
764    /// # Errors
765    ///
766    ///  * [`LsmError::ExistsInitiator`][1]: Specified initiator is used by
767    ///    other access group.
768    ///
769    /// [1]: enum.LsmError.html#variant.ExistsInitiator
770    pub fn access_group_init_add(
771        &mut self,
772        ag: &AccessGroup,
773        init_id: &str,
774        init_type: InitiatorType,
775    ) -> Result<AccessGroup> {
776        verify_init_id_str(init_id, init_type)?;
777        let mut args = Map::new();
778        args.insert("access_group".to_string(), serde_json::to_value(ag)?);
779        args.insert("init_id".to_string(), serde_json::to_value(init_id)?);
780        args.insert(
781            "init_type".to_string(),
782            serde_json::to_value(init_type as i32)?,
783        );
784        Ok(serde_json::from_value(
785            self.tp.invoke("access_group_initiator_add", Some(args))?,
786        )?)
787    }
788
789    /// Delete an initiator from an access group.
790    ///
791    /// # Errors
792    ///
793    ///  * [`LsmError::LastInitInAccessGroup`][1]: Specified initiator is the
794    ///     last initiator of access group. Use [`Client::access_group_delete()`][2] instead.
795    ///
796    /// [1]: enum.LsmError.html#variant.LastInitInAccessGroup
797    /// [2]: #method.access_group_delete
798    pub fn access_group_init_del(
799        &mut self,
800        ag: &AccessGroup,
801        init_id: &str,
802        init_type: InitiatorType,
803    ) -> Result<AccessGroup> {
804        verify_init_id_str(init_id, init_type)?;
805        let mut args = Map::new();
806        args.insert("access_group".to_string(), serde_json::to_value(ag)?);
807        args.insert("init_id".to_string(), serde_json::to_value(init_id)?);
808        args.insert(
809            "init_type".to_string(),
810            serde_json::to_value(init_type as i32)?,
811        );
812        Ok(serde_json::from_value(
813            self.tp
814                .invoke("access_group_initiator_delete", Some(args))?,
815        )?)
816    }
817
818    /// Query volumes that the specified access group has access to.
819    pub fn vols_masked_to_ag(&mut self, ag: &AccessGroup) -> Result<Vec<Volume>> {
820        let mut args = Map::new();
821        args.insert("access_group".to_string(), serde_json::to_value(ag)?);
822        Ok(serde_json::from_value(self.tp.invoke(
823            "volumes_accessible_by_access_group",
824            Some(args),
825        )?)?)
826    }
827
828    /// Retrieves the access groups that have access to the specified volume.
829    pub fn ags_granted_to_vol(&mut self, vol: &Volume) -> Result<Vec<AccessGroup>> {
830        let mut args = Map::new();
831        args.insert("volume".to_string(), serde_json::to_value(vol)?);
832        Ok(serde_json::from_value(
833            self.tp
834                .invoke("access_groups_granted_to_volume", Some(args))?,
835        )?)
836    }
837
838    /// Check whether volume has child dependencies.
839    pub fn vol_has_child_dep(&mut self, vol: &Volume) -> Result<bool> {
840        let mut args = Map::new();
841        args.insert("volume".to_string(), serde_json::to_value(vol)?);
842        Ok(serde_json::from_value(
843            self.tp.invoke("volume_child_dependency", Some(args))?,
844        )?)
845    }
846
847    /// Delete all child dependencies of the specified volume.
848    ///
849    /// Instruct storage system to remove all child dependencies of the
850    /// specified volume by duplicating the required storage before breaking
851    /// replication relationship. This function might take a long time(days or
852    /// even weeks), you might want to invoke it in a thread.
853    pub fn vol_child_dep_rm(&mut self, vol: &Volume) -> Result<()> {
854        let mut args = Map::new();
855        args.insert("volume".to_string(), serde_json::to_value(vol)?);
856        let ret = self.tp.invoke("volume_child_dependency_rm", Some(args))?;
857        self.wait_if_async(&ret)
858    }
859
860    /// Create a new file system.
861    ///
862    ///  * `pool` -- The pool where new file system should allocated from.
863    ///  * `name` -- The name of new file system. It might be altered or
864    ///    ignored.
865    ///  * `size_bytes` -- Size in bytes of new file system. You may use
866    ///    function [`size_human_2_size_bytes()`][1] to convert string like
867    ///    '1.1 GiB' to integer size bytes.
868    ///
869    /// [1]: fn.size_human_2_size_bytes.html
870    pub fn fs_create(&mut self, pool: &Pool, name: &str, size_bytes: u64) -> Result<FileSystem> {
871        let mut args = Map::new();
872        args.insert("size_bytes".to_string(), serde_json::to_value(size_bytes)?);
873        args.insert("name".to_string(), serde_json::to_value(name)?);
874        args.insert("pool".to_string(), serde_json::to_value(pool)?);
875
876        let ret = self.tp.invoke("fs_create", Some(args))?;
877        self.get_fs_from_async(&ret)
878    }
879
880    /// Resize of file system.
881    pub fn fs_resize(&mut self, fs: &FileSystem, new_size_bytes: u64) -> Result<FileSystem> {
882        let mut args = Map::new();
883        args.insert("fs".to_string(), serde_json::to_value(fs)?);
884        args.insert(
885            "new_size_bytes".to_string(),
886            serde_json::to_value(new_size_bytes)?,
887        );
888        let ret = self.tp.invoke("fs_resize", Some(args))?;
889        self.get_fs_from_async(&ret)
890    }
891
892    /// Delete a file system.
893    ///
894    /// When file system has snapshot attached, all its snapshot will be
895    /// deleted also. When file system is exported, all its exports will be
896    /// deleted also. If specified file system is has child dependency, it
897    /// cannot be deleted, please use [`Client::fs_has_child_dep()`][1] and
898    /// [`Client::fs_child_dep_rm()`][2].
899    ///
900    /// [1]: #method.fs_has_child_dep()
901    /// [2]: #method.fs_child_dep_rm()
902    pub fn fs_delete(&mut self, fs: &FileSystem) -> Result<()> {
903        let mut args = Map::new();
904        args.insert("fs".to_string(), serde_json::to_value(fs)?);
905        let ret = self.tp.invoke("fs_delete", Some(args))?;
906        self.wait_if_async(&ret)
907    }
908
909    /// Clones an existing file system
910    ///
911    /// Create a point in time read writeable space efficient copy of specified
912    /// file system, also know as read writeable snapshot. The new file system
913    /// will reside in the same pool of specified file system.
914    ///
915    /// Optionally, new file system could be based on a snapshot specified by
916    /// `snapshot` argument.
917    pub fn fs_clone(
918        &mut self,
919        src_fs: &FileSystem,
920        dst_fs_name: &str,
921        snapshot: Option<&FileSystemSnapShot>,
922    ) -> Result<FileSystem> {
923        let mut args = Map::new();
924        args.insert("src_fs".to_string(), serde_json::to_value(src_fs)?);
925        args.insert(
926            "dest_fs_name".to_string(),
927            serde_json::to_value(dst_fs_name)?,
928        );
929        args.insert("snapshot".to_string(), serde_json::to_value(snapshot)?);
930
931        let ret = self.tp.invoke("fs_clone", Some(args))?;
932        self.get_fs_from_async(&ret)
933    }
934
935    /// Clones a file on a file system.
936    ///
937    /// Optionally, file contents could be based on a snapshot specified by
938    /// `snapshot` argument.
939    pub fn fs_file_clone(
940        &mut self,
941        fs: &FileSystem,
942        src_file_name: &str,
943        dst_file_name: &str,
944        snapshot: Option<&FileSystemSnapShot>,
945    ) -> Result<()> {
946        let mut args = Map::new();
947        args.insert("fs".to_string(), serde_json::to_value(fs)?);
948        args.insert(
949            "src_file_name".to_string(),
950            serde_json::to_value(src_file_name)?,
951        );
952        args.insert(
953            "dest_file_name".to_string(),
954            serde_json::to_value(dst_file_name)?,
955        );
956        args.insert("snapshot".to_string(), serde_json::to_value(snapshot)?);
957
958        let ret = self.tp.invoke("fs_file_clone", Some(args))?;
959        self.wait_if_async(&ret)
960    }
961
962    /// Get a list of snapshots of specified file system.
963    pub fn fs_snapshots(&mut self, fs: &FileSystem) -> Result<Vec<FileSystemSnapShot>> {
964        let mut args = Map::new();
965        args.insert("fs".to_string(), serde_json::to_value(fs)?);
966        Ok(serde_json::from_value(
967            self.tp.invoke("fs_snapshots", Some(args))?,
968        )?)
969    }
970
971    /// Create a file system snapshot.
972    pub fn fs_snapshot_create(
973        &mut self,
974        fs: &FileSystem,
975        name: &str,
976    ) -> Result<FileSystemSnapShot> {
977        let mut args = Map::new();
978        args.insert("fs".to_string(), serde_json::to_value(fs)?);
979        args.insert("snapshot_name".to_string(), serde_json::to_value(name)?);
980        let ret = self.tp.invoke("fs_snapshot_create", Some(args))?;
981        self.get_fs_snap_from_asyn(&ret)
982    }
983
984    /// Delete a file system snapshot.
985    pub fn fs_snapshot_delete(
986        &mut self,
987        fs: &FileSystem,
988        snapshot: &FileSystemSnapShot,
989    ) -> Result<()> {
990        let mut args = Map::new();
991        args.insert("fs".to_string(), serde_json::to_value(fs)?);
992        args.insert("snapshot".to_string(), serde_json::to_value(snapshot)?);
993        let ret = self.tp.invoke("fs_snapshot_delete", Some(args))?;
994        self.wait_if_async(&ret)
995    }
996
997    /// Restore a file system based on specified snapshot.
998    ///
999    ///  * `fs` -- File system to restore.
1000    ///  * `snapshot` -- Snapshot to use.
1001    ///  * `all_file` -- `true` for restore all files. `false` for restore
1002    ///    specified files only.
1003    ///  * `files` -- Only restored specified files. Ignored if `all_file` is
1004    ///    `true`.
1005    ///  * `restore_files` -- If not `None`, rename restored files to defined
1006    ///    file paths and names
1007    pub fn fs_snapshot_restore(
1008        &mut self,
1009        fs: &FileSystem,
1010        snapshot: &FileSystemSnapShot,
1011        all_file: bool,
1012        files: Option<&[&str]>,
1013        restore_files: Option<&[&str]>,
1014    ) -> Result<()> {
1015        let mut args = Map::new();
1016        if all_file {
1017            let files: [&str; 0] = [];
1018            let restore_files: [&str; 0] = [];
1019            args.insert("files".to_string(), serde_json::to_value(files)?);
1020            args.insert(
1021                "restore_files".to_string(),
1022                serde_json::to_value(restore_files)?,
1023            );
1024        } else {
1025            let files = files.unwrap_or(&[]);
1026            if files.is_empty() {
1027                return Err(LsmError::InvalidArgument(
1028                    "Invalid argument: `all_file` is false while \
1029                     `files` is empty"
1030                        .to_string(),
1031                ));
1032            }
1033            let restore_files = restore_files.unwrap_or(&[]);
1034            if !restore_files.is_empty() && files.len() != restore_files.len() {
1035                return Err(LsmError::InvalidArgument(
1036                    "Invalid argument: `files` and `restore_files` have \
1037                     different lengths"
1038                        .to_string(),
1039                ));
1040            }
1041            args.insert("files".to_string(), serde_json::to_value(files)?);
1042            args.insert(
1043                "restore_files".to_string(),
1044                serde_json::to_value(restore_files)?,
1045            );
1046        }
1047
1048        args.insert("fs".to_string(), serde_json::to_value(fs)?);
1049        args.insert("snapshot".to_string(), serde_json::to_value(snapshot)?);
1050        args.insert("all_files".to_string(), serde_json::to_value(all_file)?);
1051        let ret = self.tp.invoke("fs_snapshot_restore", Some(args))?;
1052        self.wait_if_async(&ret)
1053    }
1054
1055    /// Checks whether file system has a child dependency.
1056    pub fn fs_has_child_dep(&mut self, fs: &FileSystem, files: Option<Vec<&str>>) -> Result<bool> {
1057        let mut args = Map::new();
1058        args.insert("fs".to_string(), serde_json::to_value(fs)?);
1059        let files: Vec<&str> = files.unwrap_or_default();
1060        args.insert("files".to_string(), serde_json::to_value(files)?);
1061        Ok(serde_json::from_value(
1062            self.tp.invoke("fs_child_dependency", Some(args))?,
1063        )?)
1064    }
1065
1066    /// Delete all child dependencies of the specified file system.
1067    ///
1068    /// Instruct storage system to remove all child dependencies of the
1069    /// specified file system by duplicating the required storage before
1070    /// breaking replication relationship. This function might take a long
1071    /// time(days or even weeks), you might want to invoke it in a thread.
1072    pub fn fs_child_dep_rm(&mut self, fs: &FileSystem, files: Option<Vec<&str>>) -> Result<()> {
1073        let mut args = Map::new();
1074        args.insert("fs".to_string(), serde_json::to_value(fs)?);
1075        let files: Vec<&str> = files.unwrap_or_default();
1076        args.insert("files".to_string(), serde_json::to_value(files)?);
1077        let ret = self.tp.invoke("fs_child_dependency_rm", Some(args))?;
1078        self.wait_if_async(&ret)
1079    }
1080
1081    /// Get supported NFS client authentication types.
1082    pub fn nfs_exp_auth_type_list(&mut self) -> Result<Vec<String>> {
1083        Ok(serde_json::from_value(
1084            self.tp.invoke("export_auth", None)?,
1085        )?)
1086    }
1087
1088    /// Create or modify an NFS export.
1089    ///
1090    /// * `fs` -- File system to export.
1091    /// * `export_path` -- Export path. If already exists, will modify exist NFS
1092    ///   export. If `None`, will let storage system to generate one.
1093    /// * `access` -- NFS access details.
1094    /// * `auth_type` -- NFS client authentication type. Get from
1095    ///   [`Client::nfs_exp_auth_type_list()`][1].
1096    /// * `options` -- Extra NFS options.
1097    ///
1098    /// [1]: #method.nfs_exp_auth_type_list
1099    pub fn fs_export(
1100        &mut self,
1101        fs: &FileSystem,
1102        export_path: Option<&str>,
1103        access: &NfsAccess,
1104        auth_type: Option<&str>,
1105        options: Option<&str>,
1106    ) -> Result<NfsExport> {
1107        let root_list = access.root_list;
1108        let rw_list = access.rw_list;
1109        let ro_list = access.ro_list;
1110
1111        if rw_list.is_empty() && ro_list.is_empty() {
1112            return Err(LsmError::InvalidArgument(
1113                "At least one host should exists in `rw_list` or `ro_list`".to_string(),
1114            ));
1115        }
1116        for host in root_list {
1117            if !rw_list.contains(host) && !ro_list.contains(host) {
1118                return Err(LsmError::InvalidArgument(format!(
1119                    "Host defined in `root_list` should be also \
1120                     defined in `rw_list` or `ro_list`: '{}'",
1121                    host
1122                )));
1123            }
1124        }
1125        for host in rw_list {
1126            if ro_list.contains(host) {
1127                return Err(LsmError::InvalidArgument(format!(
1128                    "Host should not both in `rw_list` \
1129                     and `ro_list`: '{}'",
1130                    host
1131                )));
1132            }
1133        }
1134
1135        let mut args = Map::new();
1136        args.insert("fs_id".to_string(), serde_json::to_value(&fs.id)?);
1137        args.insert(
1138            "export_path".to_string(),
1139            serde_json::to_value(export_path)?,
1140        );
1141        args.insert("root_list".to_string(), serde_json::to_value(root_list)?);
1142        args.insert("rw_list".to_string(), serde_json::to_value(rw_list)?);
1143        args.insert("ro_list".to_string(), serde_json::to_value(ro_list)?);
1144
1145        let anon_uid = access.anon_uid.unwrap_or(NfsExport::ANON_UID_GID_NA);
1146        let anon_gid = access.anon_gid.unwrap_or(NfsExport::ANON_UID_GID_NA);
1147        args.insert("anon_uid".to_string(), serde_json::to_value(anon_uid)?);
1148        args.insert("anon_gid".to_string(), serde_json::to_value(anon_gid)?);
1149        args.insert("auth_type".to_string(), serde_json::to_value(auth_type)?);
1150        args.insert("options".to_string(), serde_json::to_value(options)?);
1151        Ok(serde_json::from_value(
1152            self.tp.invoke("export_fs", Some(args))?,
1153        )?)
1154    }
1155
1156    /// Unexport specified NFS exports.
1157    pub fn fs_unexport(&mut self, exp: &NfsExport) -> Result<()> {
1158        let mut args = Map::new();
1159        args.insert("export".to_string(), serde_json::to_value(exp)?);
1160        Ok(serde_json::from_value(
1161            self.tp.invoke("export_remove", Some(args))?,
1162        )?)
1163    }
1164
1165    /// Get volume RAID information.
1166    pub fn vol_raid_info(&mut self, vol: &Volume) -> Result<VolumeRaidInfo> {
1167        let mut args = Map::new();
1168        args.insert("volume".to_string(), serde_json::to_value(vol)?);
1169        let ret: Vec<i32> =
1170            serde_json::from_value(self.tp.invoke("volume_raid_info", Some(args))?)?;
1171        if ret.len() != 5 {
1172            return Err(LsmError::PluginBug(format!(
1173                "vol_raid_info() is expecting 5 integers from plugin, \
1174                 but got '{:?}'",
1175                ret
1176            )));
1177        }
1178        Ok(VolumeRaidInfo {
1179            raid_type: From::from(ret[0]),
1180            strip_size: ret[1] as u32,
1181            disk_count: ret[2] as u32,
1182            min_io_size: ret[3] as u32,
1183            opt_io_size: ret[4] as u32,
1184        })
1185    }
1186
1187    /// Get pool member information.
1188    pub fn pool_member_info(&mut self, pool: &Pool) -> Result<PoolMemberInfo> {
1189        let mut args = Map::new();
1190        args.insert("pool".to_string(), serde_json::to_value(pool)?);
1191        let ret = self.tp.invoke("pool_member_info", Some(args))?;
1192        let ret_array = ret.as_array().ok_or_plugin_bug(&ret)?;
1193        if ret_array.len() != 3 {
1194            return Err(LsmError::PluginBug(format!(
1195                "Plugin return unexpected data: {:?}",
1196                ret
1197            )));
1198        }
1199        let raid_type: i32 =
1200            serde_json::from_value(ret_array.first().ok_or_plugin_bug(&ret)?.clone())?;
1201        let raid_type: RaidType = From::from(raid_type);
1202        let member_type: u32 =
1203            serde_json::from_value(ret_array.get(1).ok_or_plugin_bug(&ret)?.clone())?;
1204        let member_ids: Vec<String> =
1205            serde_json::from_value(ret_array.get(2).ok_or_plugin_bug(&ret)?.clone())?;
1206        let mut members: Vec<PoolMember> = Vec::new();
1207        match member_type {
1208            POOL_MEMBER_TYPE_DISK => {
1209                for disk in self.disks()? {
1210                    if member_ids.contains(&disk.id) {
1211                        members.push(PoolMember::Disk(disk));
1212                    }
1213                }
1214            }
1215            POOL_MEMBER_TYPE_POOL => {
1216                for pool in self.pools()? {
1217                    if member_ids.contains(&pool.id) {
1218                        members.push(PoolMember::Pool(pool));
1219                    }
1220                }
1221            }
1222            _ => (),
1223        };
1224        Ok(PoolMemberInfo { raid_type, members })
1225    }
1226
1227    /// Get system capability on creating RAIDed volume. For hardware RAID
1228    /// only.
1229    ///
1230    /// Returns supported RAID types and strip sizes.
1231    pub fn vol_raid_create_cap_get(&mut self, sys: &System) -> Result<(Vec<RaidType>, Vec<u32>)> {
1232        let mut args = Map::new();
1233        args.insert("system".to_string(), serde_json::to_value(sys)?);
1234        let ret = self.tp.invoke("volume_raid_create_cap_get", Some(args))?;
1235        let ret_array = ret.as_array().ok_or_plugin_bug(&ret)?;
1236        if ret_array.len() != 2 {
1237            return Err(LsmError::PluginBug(format!(
1238                "vol_raid_create_cap_get() is expecting array with \
1239                 2 members from plugin, but got '{:?}'",
1240                ret
1241            )));
1242        }
1243        let raid_types: Vec<i32> =
1244            serde_json::from_value(ret_array.first().ok_or_plugin_bug(&ret)?.clone())?;
1245        let strip_sizes: Vec<u32> =
1246            serde_json::from_value(ret_array.get(1).ok_or_plugin_bug(&ret)?.clone())?;
1247        let mut new_raid_types: Vec<RaidType> = Vec::new();
1248        for raid_type in raid_types {
1249            new_raid_types.push(From::from(raid_type));
1250        }
1251        Ok((new_raid_types, strip_sizes))
1252    }
1253
1254    /// Create RAIDed volume directly from disks. Only for hardware RAID.
1255    pub fn vol_raid_create(
1256        &mut self,
1257        name: &str,
1258        raid_type: RaidType,
1259        disks: &[Disk],
1260        strip_size: Option<u32>,
1261    ) -> Result<Volume> {
1262        if disks.is_empty() {
1263            return Err(LsmError::InvalidArgument("no disk included".to_string()));
1264        }
1265
1266        if raid_type == RaidType::Raid1 && disks.len() != 2 {
1267            return Err(LsmError::InvalidArgument(
1268                "RAID 1 only allow 2 disks".to_string(),
1269            ));
1270        }
1271
1272        if raid_type == RaidType::Raid5 && disks.len() < 3 {
1273            return Err(LsmError::InvalidArgument(
1274                "RAID 5 require 3 or more disks".to_string(),
1275            ));
1276        }
1277
1278        if raid_type == RaidType::Raid6 && disks.len() < 4 {
1279            return Err(LsmError::InvalidArgument(
1280                "RAID 6 require 4 or more disks".to_string(),
1281            ));
1282        }
1283
1284        if raid_type == RaidType::Raid10 && (disks.len() % 2 != 0 || disks.len() < 4) {
1285            return Err(LsmError::InvalidArgument(
1286                "RAID 10 require even disks count and 4 or more disks".to_string(),
1287            ));
1288        }
1289
1290        if raid_type == RaidType::Raid50 && (disks.len() % 2 != 0 || disks.len() < 6) {
1291            return Err(LsmError::InvalidArgument(
1292                "RAID 50 require even disks count and 6 or more disks".to_string(),
1293            ));
1294        }
1295
1296        if raid_type == RaidType::Raid60 && (disks.len() % 2 != 0 || disks.len() < 8) {
1297            return Err(LsmError::InvalidArgument(
1298                "RAID 60 require even disks count and 8 or more disks".to_string(),
1299            ));
1300        }
1301
1302        let mut args = Map::new();
1303        args.insert("name".to_string(), serde_json::to_value(name)?);
1304        args.insert(
1305            "raid_type".to_string(),
1306            serde_json::to_value(raid_type as i32)?,
1307        );
1308        args.insert("disks".to_string(), serde_json::to_value(disks)?);
1309        let strip_size = strip_size.unwrap_or(0u32);
1310        args.insert("strip_size".to_string(), serde_json::to_value(strip_size)?);
1311        Ok(serde_json::from_value(
1312            self.tp.invoke("volume_raid_create", Some(args))?,
1313        )?)
1314    }
1315
1316    /// Turn on the identification LED for the specified volume.
1317    ///
1318    /// All its member disks' identification LED will be turned on.
1319    pub fn vol_ident_led_on(&mut self, vol: &Volume) -> Result<()> {
1320        let mut args = Map::new();
1321        args.insert("volume".to_string(), serde_json::to_value(vol)?);
1322        self.tp.invoke("volume_ident_led_on", Some(args))?;
1323        Ok(())
1324    }
1325
1326    /// Turn off the identification LED for the specified volume.
1327    ///
1328    /// All its member disks' identification LED will be turned off.
1329    pub fn vol_ident_led_off(&mut self, vol: &Volume) -> Result<()> {
1330        let mut args = Map::new();
1331        args.insert("volume".to_string(), serde_json::to_value(vol)?);
1332        self.tp.invoke("volume_ident_led_off", Some(args))?;
1333        Ok(())
1334    }
1335
1336    /// Get cache information on specified volume.
1337    pub fn vol_cache_info(&mut self, vol: &Volume) -> Result<VolumeCacheInfo> {
1338        let mut args = Map::new();
1339        args.insert("volume".to_string(), serde_json::to_value(vol)?);
1340        let ret: Vec<u8> =
1341            serde_json::from_value(self.tp.invoke("volume_cache_info", Some(args))?)?;
1342        if ret.len() != 5 {
1343            return Err(LsmError::PluginBug(format!(
1344                "vol_cache_info() is expecting 5 u8 from plugin, \
1345                 but got '{:?}'",
1346                ret
1347            )));
1348        }
1349        Ok(VolumeCacheInfo {
1350            write_cache_setting: match ret[0] {
1351                WRITE_CACHE_POLICY_WRITE_BACK => CachePolicy::Enabled,
1352                WRITE_CACHE_POLICY_WRITE_THROUGH => CachePolicy::Disabled,
1353                WRITE_CACHE_POLICY_AUTO => CachePolicy::Auto,
1354                _ => CachePolicy::Unknown,
1355            },
1356            write_cache_status: match ret[1] {
1357                WRITE_CACHE_STATUS_WRITE_BACK => CachePolicy::Enabled,
1358                WRITE_CACHE_STATUS_WRITE_THROUGH => CachePolicy::Disabled,
1359                _ => CachePolicy::Unknown,
1360            },
1361            read_cache_setting: match ret[2] {
1362                READ_CACHE_POLICY_ENABLED => CachePolicy::Enabled,
1363                READ_CACHE_POLICY_DISABLED => CachePolicy::Disabled,
1364                _ => CachePolicy::Unknown,
1365            },
1366            read_cache_status: match ret[3] {
1367                READ_CACHE_STATUS_ENABLED => CachePolicy::Enabled,
1368                READ_CACHE_STATUS_DISABLED => CachePolicy::Disabled,
1369                _ => CachePolicy::Unknown,
1370            },
1371            physical_disk_cache_status: match ret[4] {
1372                PHYSICAL_DISK_CACHE_ENABLED => CachePolicy::Enabled,
1373                PHYSICAL_DISK_CACHE_DISABLED => CachePolicy::Disabled,
1374                PHYSICAL_DISK_CACHE_USE_DISK_SETTING => CachePolicy::UseDiskSetting,
1375                _ => CachePolicy::Unknown,
1376            },
1377        })
1378    }
1379
1380    /// Set volume physical disk cache policy.
1381    pub fn vol_phy_disk_cache_set(&mut self, vol: &Volume, pdc: CachePolicy) -> Result<()> {
1382        let pdc: u8 = match pdc {
1383            CachePolicy::Enabled => PHYSICAL_DISK_CACHE_ENABLED,
1384            CachePolicy::Disabled => PHYSICAL_DISK_CACHE_DISABLED,
1385            CachePolicy::UseDiskSetting => PHYSICAL_DISK_CACHE_USE_DISK_SETTING,
1386            _ => {
1387                return Err(LsmError::InvalidArgument(format!(
1388                    "Invalid pdc argument {:?}",
1389                    pdc
1390                )))
1391            }
1392        };
1393        let mut args = Map::new();
1394        args.insert("volume".to_string(), serde_json::to_value(vol)?);
1395        args.insert("pdc".to_string(), serde_json::to_value(pdc)?);
1396        self.tp
1397            .invoke("volume_physical_disk_cache_update", Some(args))?;
1398        Ok(())
1399    }
1400
1401    /// Set volume write cache policy.
1402    pub fn vol_write_cache_set(&mut self, vol: &Volume, wcp: CachePolicy) -> Result<()> {
1403        let wcp: u8 = match wcp {
1404            CachePolicy::Enabled => WRITE_CACHE_POLICY_WRITE_BACK,
1405            CachePolicy::Disabled => WRITE_CACHE_POLICY_WRITE_THROUGH,
1406            CachePolicy::Auto => WRITE_CACHE_POLICY_AUTO,
1407            _ => {
1408                return Err(LsmError::InvalidArgument(format!(
1409                    "Invalid wcp argument {:?}",
1410                    wcp
1411                )))
1412            }
1413        };
1414        let mut args = Map::new();
1415        args.insert("volume".to_string(), serde_json::to_value(vol)?);
1416        args.insert("wcp".to_string(), serde_json::to_value(wcp)?);
1417        self.tp
1418            .invoke("volume_write_cache_policy_update", Some(args))?;
1419        Ok(())
1420    }
1421
1422    /// Set volume read cache policy.
1423    pub fn vol_read_cache_set(&mut self, vol: &Volume, rcp: CachePolicy) -> Result<()> {
1424        let rcp: u8 = match rcp {
1425            CachePolicy::Enabled => READ_CACHE_POLICY_ENABLED,
1426            CachePolicy::Disabled => READ_CACHE_POLICY_DISABLED,
1427            _ => {
1428                return Err(LsmError::InvalidArgument(format!(
1429                    "Invalid rcp argument {:?}",
1430                    rcp
1431                )))
1432            }
1433        };
1434        let mut args = Map::new();
1435        args.insert("volume".to_string(), serde_json::to_value(vol)?);
1436        args.insert("rcp".to_string(), serde_json::to_value(rcp)?);
1437        self.tp
1438            .invoke("volume_read_cache_policy_update", Some(args))?;
1439        Ok(())
1440    }
1441}