blob: 2edd5c16f431fabe91e9f9e664aeed3e5b47d61c [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
Matthew Treinish9e26ca82016-02-23 11:43:20 -050018import re
19
Anusha Raminenif3eb9472017-01-13 08:54:01 +053020from oslo_log import log as logging
21
Matthew Treinish9e26ca82016-02-23 11:43:20 -050022from tempest.lib import exceptions
23
24
25LOG = logging.getLogger(__name__)
26
27
28delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
29
30
31def details_multiple(output_lines, with_label=False):
32 """Return list of dicts with item details from cli output tables.
33
34 If with_label is True, key '__label' is added to each items dict.
35 For more about 'label' see OutputParser.tables().
36 """
37 items = []
38 tables_ = tables(output_lines)
39 for table_ in tables_:
40 if ('Property' not in table_['headers']
41 or 'Value' not in table_['headers']):
42 raise exceptions.InvalidStructure()
43 item = {}
44 for value in table_['values']:
45 item[value[0]] = value[1]
46 if with_label:
47 item['__label'] = table_['label']
48 items.append(item)
49 return items
50
51
52def details(output_lines, with_label=False):
53 """Return dict with details of first item (table) found in output."""
54 items = details_multiple(output_lines, with_label)
55 return items[0]
56
57
58def listing(output_lines):
59 """Return list of dicts with basic item info parsed from cli output."""
60
61 items = []
62 table_ = table(output_lines)
63 for row in table_['values']:
64 item = {}
65 for col_idx, col_key in enumerate(table_['headers']):
66 item[col_key] = row[col_idx]
67 items.append(item)
68 return items
69
70
71def tables(output_lines):
72 """Find all ascii-tables in output and parse them.
73
74 Return list of tables parsed from cli output as dicts.
75 (see OutputParser.table())
76
77 And, if found, label key (separated line preceding the table)
78 is added to each tables dict.
79 """
80 tables_ = []
81
82 table_ = []
83 label = None
84
85 start = False
86 header = False
87
88 if not isinstance(output_lines, list):
89 output_lines = output_lines.split('\n')
90
91 for line in output_lines:
92 if delimiter_line.match(line):
93 if not start:
94 start = True
95 elif not header:
96 # we are after head area
97 header = True
98 else:
99 # table ends here
100 start = header = None
101 table_.append(line)
102
103 parsed = table(table_)
104 parsed['label'] = label
105 tables_.append(parsed)
106
107 table_ = []
108 label = None
109 continue
110 if start:
111 table_.append(line)
112 else:
113 if label is None:
114 label = line
115 else:
Jordan Pittier525ec712016-12-07 17:51:26 +0100116 LOG.warning('Invalid line between tables: %s', line)
Masayuki Igawa0c0f0142017-04-10 17:22:02 +0900117 if table_:
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500118 LOG.warning('Missing end of table')
119
120 return tables_
121
122
123def table(output_lines):
124 """Parse single table from cli output.
125
126 Return dict with list of column names in 'headers' key and
127 rows in 'values' key.
128 """
129 table_ = {'headers': [], 'values': []}
130 columns = None
131
132 if not isinstance(output_lines, list):
133 output_lines = output_lines.split('\n')
134
135 if not output_lines[-1]:
136 # skip last line if empty (just newline at the end)
137 output_lines = output_lines[:-1]
138
139 for line in output_lines:
140 if delimiter_line.match(line):
141 columns = _table_columns(line)
142 continue
143 if '|' not in line:
Jordan Pittier525ec712016-12-07 17:51:26 +0100144 LOG.warning('skipping invalid table line: %s', line)
Matthew Treinish9e26ca82016-02-23 11:43:20 -0500145 continue
146 row = []
147 for col in columns:
148 row.append(line[col[0]:col[1]].strip())
149 if table_['headers']:
150 table_['values'].append(row)
151 else:
152 table_['headers'] = row
153
154 return table_
155
156
157def _table_columns(first_table_row):
158 """Find column ranges in output line.
159
160 Return list of tuples (start,end) for each column
161 detected by plus (+) characters in delimiter line.
162 """
163 positions = []
164 start = 1 # there is '+' at 0
165 while start < len(first_table_row):
166 end = first_table_row.find('+', start)
167 if end == -1:
168 break
169 positions.append((start, end))
170 start = end + 1
171 return positions