3 use anyhow
::{bail, Error}
;
4 use serde
::{Deserialize, Serialize}
;
5 use serde_json
::{Map, Value}
;
7 use crate::tools
::nom
::{
8 parse_complete
, parse_error
, parse_failure
,
9 multispace0
, multispace1
, notspace1
, parse_u64
, IResult
,
13 bytes
::complete
::{tag, take_while, take_while1}
,
16 character
::complete
::{line_ending}
,
21 #[derive(Debug, Serialize, Deserialize)]
22 pub struct ZFSPoolVDevState
{
25 #[serde(skip_serializing_if="Option::is_none")]
26 pub state
: Option
<String
>,
27 #[serde(skip_serializing_if="Option::is_none")]
28 pub read
: Option
<u64>,
29 #[serde(skip_serializing_if="Option::is_none")]
30 pub write
: Option
<u64>,
31 #[serde(skip_serializing_if="Option::is_none")]
32 pub cksum
: Option
<u64>,
33 #[serde(skip_serializing_if="Option::is_none")]
34 pub msg
: Option
<String
>,
37 fn expand_tab_length(input
: &str) -> usize {
38 input
.chars().map(|c
| if c
== '
\t' { 8 }
else { 1 }
).sum()
41 fn parse_zpool_status_vdev(i
: &str) -> IResult
<&str, ZFSPoolVDevState
> {
43 let (n
, indent
) = multispace0(i
)?
;
45 let indent_len
= expand_tab_length(indent
);
47 if (indent_len
& 1) != 0 {
48 return Err(parse_failure(n
, "wrong indent length"));
52 let indent_level
= (indent_len
as u64)/2;
54 let (i
, vdev_name
) = notspace1(i
)?
;
56 if let Ok((n
, _
)) = preceded(multispace0
, line_ending
)(i
) { // special device
57 let vdev
= ZFSPoolVDevState
{
58 name
: vdev_name
.to_string(),
69 let (i
, state
) = preceded(multispace1
, notspace1
)(i
)?
;
70 if let Ok((n
, _
)) = preceded(multispace0
, line_ending
)(i
) { // spares
71 let vdev
= ZFSPoolVDevState
{
72 name
: vdev_name
.to_string(),
74 state
: Some(state
.to_string()),
83 let (i
, read
) = preceded(multispace1
, parse_u64
)(i
)?
;
84 let (i
, write
) = preceded(multispace1
, parse_u64
)(i
)?
;
85 let (i
, cksum
) = preceded(multispace1
, parse_u64
)(i
)?
;
86 let (i
, msg
) = opt(preceded(multispace1
, take_while(|c
| c
!= '
\n'
)))(i
)?
;
87 let (i
, _
) = line_ending(i
)?
;
89 let vdev
= ZFSPoolVDevState
{
90 name
: vdev_name
.to_string(),
92 state
: Some(state
.to_string()),
96 msg
: msg
.map(String
::from
),
102 fn parse_zpool_status_tree(i
: &str) -> IResult
<&str, Vec
<ZFSPoolVDevState
>> {
105 let (i
, _
) = tag("NAME")(i
)?
;
106 let (i
, _
) = multispace1(i
)?
;
107 let (i
, _
) = tag("STATE")(i
)?
;
108 let (i
, _
) = multispace1(i
)?
;
109 let (i
, _
) = tag("READ")(i
)?
;
110 let (i
, _
) = multispace1(i
)?
;
111 let (i
, _
) = tag("WRITE")(i
)?
;
112 let (i
, _
) = multispace1(i
)?
;
113 let (i
, _
) = tag("CKSUM")(i
)?
;
114 let (i
, _
) = line_ending(i
)?
;
117 many1(parse_zpool_status_vdev
)(i
)
120 fn space_indented_line(indent
: usize) -> impl Fn(&str) -> IResult
<&str, &str> {
125 if n
.starts_with('
\t'
) {
127 } else if n
.starts_with(' '
) {
133 if len
>= indent { break; }
136 return Err(parse_error(i
, "not correctly indented"));
139 take_while1(|c
| c
!= '
\n'
)(n
)
143 fn parse_zpool_status_field(i
: &str) -> IResult
<&str, (String
, String
)> {
144 let (i
, prefix
) = take_while1(|c
| c
!= '
:'
)(i
)?
;
145 let (i
, _
) = tag(":")(i
)?
;
146 let (i
, mut value
) = take_while(|c
| c
!= '
\n'
)(i
)?
;
147 if value
.starts_with(' '
) { value = &value[1..]; }
149 let (mut i
, _
) = line_ending(i
)?
;
151 let field
= prefix
.trim().to_string();
153 let prefix_len
= expand_tab_length(prefix
);
155 let indent
: usize = prefix_len
+ 2;
157 let parse_continuation
= opt(space_indented_line(indent
));
159 let mut value
= value
.to_string();
161 if field
== "config" {
162 let (n
, _
) = line_ending(i
)?
;
167 let (n
, cont
) = parse_continuation(i
)?
;
169 if let Some(cont
) = cont
{
170 let (n
, _
) = line_ending(n
)?
;
172 if !value
.is_empty() { value.push('\n'); }
173 value
.push_str(cont
);
175 if field
== "config" {
176 let (n
, _
) = line_ending(i
)?
;
184 Ok((i
, (field
, value
)))
187 pub fn parse_zpool_status_config_tree(i
: &str) -> Result
<Vec
<ZFSPoolVDevState
>, Error
> {
188 parse_complete("zfs status config tree", i
, parse_zpool_status_tree
)
191 fn parse_zpool_status(input
: &str) -> Result
<Vec
<(String
, String
)>, Error
> {
192 parse_complete("zfs status output", &input
, many0(parse_zpool_status_field
))
195 pub fn vdev_list_to_tree(vdev_list
: &[ZFSPoolVDevState
]) -> Result
<Value
, Error
> {
196 indented_list_to_tree(vdev_list
, |vdev
| {
197 let node
= serde_json
::to_value(vdev
).unwrap();
202 fn indented_list_to_tree
<'a
, T
, F
, I
>(items
: I
, to_node
: F
) -> Result
<Value
, Error
>
205 I
: IntoIterator
<Item
= &'a T
>,
206 F
: Fn(&T
) -> (Value
, u64),
209 node
: Map
<String
, Value
>,
211 children_of_parent
: Vec
<Value
>,
214 let mut stack
= Vec
::<StackItem
>::new();
215 // hold current node and the children of the current parent (as that's where we insert)
216 let mut cur
= StackItem
{
217 node
: Map
::<String
, Value
>::new(),
219 children_of_parent
: Vec
::new(),
223 let (node
, node_level
) = to_node(&item
);
224 let vdev_level
= 1 + node_level
;
225 let mut node
= match node
{
226 Value
::Object(map
) => map
,
227 _
=> bail
!("to_node returned wrong type"),
230 node
.insert("leaf".to_string(), Value
::Bool(true));
232 // if required, go back up (possibly multiple levels):
233 while vdev_level
< cur
.level
{
234 cur
.children_of_parent
.push(Value
::Object(cur
.node
));
235 let mut parent
= stack
.pop().unwrap();
236 parent
.node
.insert("children".to_string(), Value
::Array(cur
.children_of_parent
));
237 parent
.node
.insert("leaf".to_string(), Value
::Bool(false));
240 if vdev_level
> cur
.level
{
241 // when we encounter misimatching levels like "0, 2, 1" instead of "0, 1, 2, 1"
242 bail
!("broken indentation between levels");
246 if vdev_level
> cur
.level
{
247 // indented further, push our current state and start a new "map"
248 stack
.push(StackItem
{
249 node
: replace(&mut cur
.node
, node
),
250 level
: replace(&mut cur
.level
, vdev_level
),
251 children_of_parent
: replace(&mut cur
.children_of_parent
, Vec
::new()),
254 // same indentation level, add to children of the previous level:
255 cur
.children_of_parent
.push(Value
::Object(
256 replace(&mut cur
.node
, node
),
261 while !stack
.is_empty() {
262 cur
.children_of_parent
.push(Value
::Object(cur
.node
));
263 let mut parent
= stack
.pop().unwrap();
264 parent
.node
.insert("children".to_string(), Value
::Array(cur
.children_of_parent
));
265 parent
.node
.insert("leaf".to_string(), Value
::Bool(false));
269 Ok(Value
::Object(cur
.node
))
273 fn test_vdev_list_to_tree() {
274 const DEFAULT
: ZFSPoolVDevState
= ZFSPoolVDevState
{
285 //ZFSPoolVDevState { name: "root".to_string(), lvl: 0, ..DEFAULT },
286 ZFSPoolVDevState { name: "vdev1".to_string(), lvl: 1, ..DEFAULT }
,
287 ZFSPoolVDevState { name: "vdev1-disk1".to_string(), lvl: 2, ..DEFAULT }
,
288 ZFSPoolVDevState { name: "vdev1-disk2".to_string(), lvl: 2, ..DEFAULT }
,
289 ZFSPoolVDevState { name: "vdev2".to_string(), lvl: 1, ..DEFAULT }
,
290 ZFSPoolVDevState { name: "vdev2-g1".to_string(), lvl: 2, ..DEFAULT }
,
291 ZFSPoolVDevState { name: "vdev2-g1-d1".to_string(), lvl: 3, ..DEFAULT }
,
292 ZFSPoolVDevState { name: "vdev2-g1-d2".to_string(), lvl: 3, ..DEFAULT }
,
293 ZFSPoolVDevState { name: "vdev2-g2".to_string(), lvl: 2, ..DEFAULT }
,
294 ZFSPoolVDevState { name: "vdev3".to_string(), lvl: 1, ..DEFAULT }
,
295 ZFSPoolVDevState { name: "vdev4".to_string(), lvl: 1, ..DEFAULT }
,
296 ZFSPoolVDevState { name: "vdev4-g1".to_string(), lvl: 2, ..DEFAULT }
,
297 ZFSPoolVDevState { name: "vdev4-g1-d1".to_string(), lvl: 3, ..DEFAULT }
,
298 ZFSPoolVDevState { name: "vdev4-g1-d1-x1".to_string(), lvl: 4, ..DEFAULT }
,
299 ZFSPoolVDevState { name: "vdev4-g2".to_string(), lvl: 2, ..DEFAULT }
, // up by 2
302 const EXPECTED
: &str = "{\
306 \"lvl\":2,\"name\":\"vdev1-disk1\"\
309 \"lvl\":2,\"name\":\"vdev1-disk2\"\
312 \"lvl\":1,\"name\":\"vdev1\"\
317 \"lvl\":3,\"name\":\"vdev2-g1-d1\"\
320 \"lvl\":3,\"name\":\"vdev2-g1-d2\"\
323 \"lvl\":2,\"name\":\"vdev2-g1\"\
326 \"lvl\":2,\"name\":\"vdev2-g2\"\
329 \"lvl\":1,\"name\":\"vdev2\"\
332 \"lvl\":1,\"name\":\"vdev3\"\
338 \"lvl\":4,\"name\":\"vdev4-g1-d1-x1\"\
341 \"lvl\":3,\"name\":\"vdev4-g1-d1\"\
344 \"lvl\":2,\"name\":\"vdev4-g1\"\
347 \"lvl\":2,\"name\":\"vdev4-g2\"\
350 \"lvl\":1,\"name\":\"vdev4\"\
354 let expected
: Value
= serde_json
::from_str(EXPECTED
)
355 .expect("failed to parse expected json value");
357 let tree
= vdev_list_to_tree(&input
)
358 .expect("failed to turn valid vdev list into a tree");
359 assert_eq
!(tree
, expected
);
362 pub fn zpool_status(pool
: &str) -> Result
<Vec
<(String
, String
)>, Error
> {
364 let mut command
= std
::process
::Command
::new("zpool");
365 command
.args(&["status", "-p", "-P", pool
]);
367 let output
= pbs_tools
::run_command(command
, None
)?
;
369 parse_zpool_status(&output
)
373 fn test_parse(output
: &str) -> Result
<(), Error
> {
374 let mut found_config
= false;
376 for (k
, v
) in parse_zpool_status(&output
)?
{
377 println
!("<{}> => '{}'", k
, v
);
379 let vdev_list
= parse_zpool_status_config_tree(&v
)?
;
380 let _tree
= vdev_list_to_tree(&vdev_list
);
385 bail
!("got zpool status without config key");
392 fn test_zpool_status_parser() -> Result
<(), Error
> {
394 let output
= r
###" pool: tank
396 status: One or more devices could not be opened. Sufficient replicas exist for
397 the pool to continue functioning in a degraded state.
398 action: Attach the missing device and online it using 'zpool online'.
399 see: http://www.sun.com/msg/ZFS-8000-2Q
400 scrub: none requested
403 NAME STATE READ WRITE CKSUM
405 mirror-0 DEGRADED 0 0 0
408 c1t1d0 UNAVAIL 0 0 0 cannot open
409 mirror-1 DEGRADED 0 0 0
413 errors: No known data errors
420 fn test_zpool_status_parser2() -> Result
<(), Error
> {
422 // Note: this input create TABS
423 let output
= r
###" pool: btest
428 NAME STATE READ WRITE CKSUM
430 mirror-0 ONLINE 0 0 0
431 /dev/sda1 ONLINE 0 0 0
432 /dev/sda2 ONLINE 0 0 0
433 mirror-1 ONLINE 0 0 0
434 /dev/sda3 ONLINE 0 0 0
435 /dev/sda4 ONLINE 0 0 0
437 /dev/sda5 ONLINE 0 0 0
439 errors: No known data errors
445 fn test_zpool_status_parser3() -> Result
<(), Error
> {
447 let output
= r
###" pool: bt-est
452 NAME STATE READ WRITE CKSUM
454 mirror-0 ONLINE 0 0 0
455 /dev/sda1 ONLINE 0 0 0
456 /dev/sda2 ONLINE 0 0 0
457 mirror-1 ONLINE 0 0 0
458 /dev/sda3 ONLINE 0 0 0
459 /dev/sda4 ONLINE 0 0 0
461 /dev/sda5 ONLINE 0 0 0
463 errors: No known data errors
470 fn test_zpool_status_parser_spares() -> Result
<(), Error
> {
472 let output
= r
###" pool: tank
477 NAME STATE READ WRITE CKSUM
479 mirror-0 ONLINE 0 0 0
480 /dev/sda1 ONLINE 0 0 0
481 /dev/sda2 ONLINE 0 0 0
482 mirror-1 ONLINE 0 0 0
483 /dev/sda3 ONLINE 0 0 0
484 /dev/sda4 ONLINE 0 0 0
486 /dev/sda5 ONLINE 0 0 0
491 errors: No known data errors