blob: 031350590425b3a81af1ee42e2b53b4bde4e0fa3 [file] [log] [blame]
Matthew Treinish9e26ca82016-02-23 11:43:20 -05001# Copyright 2013 OpenStack Foundation
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# 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, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16"""Collection of utilities for parsing CLI clients output."""
17
18import logging
19import re
20
21from tempest.lib import exceptions
22
23
24LOG = logging.getLogger(__name__)
25
26
27delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
28
29
30def details_multiple(output_lines, with_label=False):
31 """Return list of dicts with item details from cli output tables.
32
33 If with_label is True, key '__label' is added to each items dict.
34 For more about 'label' see OutputParser.tables().
35 """
36 items = []
37 tables_ = tables(output_lines)
38 for table_ in tables_:
39 if ('Property' not in table_['headers']
40 or 'Value' not in table_['headers']):
41 raise exceptions.InvalidStructure()
42 item = {}
43 for value in table_['values']:
44 item[value[0]] = value[1]
45 if with_label:
46 item['__label'] = table_['label']
47 items.append(item)
48 return items
49
50
51def details(output_lines, with_label=False):
52 """Return dict with details of first item (table) found in output."""
53 items = details_multiple(output_lines, with_label)
54 return items[0]
55
56
57def listing(output_lines):
58 """Return list of dicts with basic item info parsed from cli output."""
59
60 items = []
61 table_ = table(output_lines)
62 for row in table_['values']:
63 item = {}
64 for col_idx, col_key in enumerate(table_['headers']):
65 item[col_key] = row[col_idx]
66 items.append(item)
67 return items
68
69
70def tables(output_lines):
71 """Find all ascii-tables in output and parse them.
72
73 Return list of tables parsed from cli output as dicts.
74 (see OutputParser.table())
75
76 And, if found, label key (separated line preceding the table)
77 is added to each tables dict.
78 """
79 tables_ = []
80
81 table_ = []
82 label = None
83
84 start = False
85 header = False
86
87 if not isinstance(output_lines, list):
88 output_lines = output_lines.split('\n')
89
90 for line in output_lines:
91 if delimiter_line.match(line):
92 if not start:
93 start = True
94 elif not header:
95 # we are after head area
96 header = True
97 else:
98 # table ends here
99 start = header = None
100 table_.append(line)
101
102 parsed = table(table_)
103 parsed['label'] = label
104 tables_.append(parsed)
105
106 table_ = []
107 label = None
108 continue
109 if start:
110 table_.append(line)
111 else:
112 if label is None:
113 label = line
114 else:
115 LOG.warning('Invalid line between tables: %s' % line)
116 if len(table_) > 0:
117 LOG.warning('Missing end of table')
118
119 return tables_
120
121
122def table(output_lines):
123 """Parse single table from cli output.
124
125 Return dict with list of column names in 'headers' key and
126 rows in 'values' key.
127 """
128 table_ = {'headers': [], 'values': []}
129 columns = None
130
131 if not isinstance(output_lines, list):
132 output_lines = output_lines.split('\n')
133
134 if not output_lines[-1]:
135 # skip last line if empty (just newline at the end)
136 output_lines = output_lines[:-1]
137
138 for line in output_lines:
139 if delimiter_line.match(line):
140 columns = _table_columns(line)
141 continue
142 if '|' not in line:
143 LOG.warning('skipping invalid table line: %s' % line)
144 continue
145 row = []
146 for col in columns:
147 row.append(line[col[0]:col[1]].strip())
148 if table_['headers']:
149 table_['values'].append(row)
150 else:
151 table_['headers'] = row
152
153 return table_
154
155
156def _table_columns(first_table_row):
157 """Find column ranges in output line.
158
159 Return list of tuples (start,end) for each column
160 detected by plus (+) characters in delimiter line.
161 """
162 positions = []
163 start = 1 # there is '+' at 0
164 while start < len(first_table_row):
165 end = first_table_row.find('+', start)
166 if end == -1:
167 break
168 positions.append((start, end))
169 start = end + 1
170 return positions