root / lib / qlang.py @ a292020f
History | View | Annotate | Download (7 kB)
1 | a123dc19 | Michael Hanselmann | #
|
---|---|---|---|
2 | a123dc19 | Michael Hanselmann | #
|
3 | a123dc19 | Michael Hanselmann | |
4 | 341ff8e9 | Michael Hanselmann | # Copyright (C) 2010, 2011 Google Inc.
|
5 | a123dc19 | Michael Hanselmann | #
|
6 | a123dc19 | Michael Hanselmann | # This program is free software; you can redistribute it and/or modify
|
7 | a123dc19 | Michael Hanselmann | # it under the terms of the GNU General Public License as published by
|
8 | a123dc19 | Michael Hanselmann | # the Free Software Foundation; either version 2 of the License, or
|
9 | a123dc19 | Michael Hanselmann | # (at your option) any later version.
|
10 | a123dc19 | Michael Hanselmann | #
|
11 | a123dc19 | Michael Hanselmann | # This program is distributed in the hope that it will be useful, but
|
12 | a123dc19 | Michael Hanselmann | # WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 | a123dc19 | Michael Hanselmann | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 | a123dc19 | Michael Hanselmann | # General Public License for more details.
|
15 | a123dc19 | Michael Hanselmann | #
|
16 | a123dc19 | Michael Hanselmann | # You should have received a copy of the GNU General Public License
|
17 | a123dc19 | Michael Hanselmann | # along with this program; if not, write to the Free Software
|
18 | a123dc19 | Michael Hanselmann | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 | a123dc19 | Michael Hanselmann | # 02110-1301, USA.
|
20 | a123dc19 | Michael Hanselmann | |
21 | a123dc19 | Michael Hanselmann | |
22 | 341ff8e9 | Michael Hanselmann | """Module for a simple query language
|
23 | 341ff8e9 | Michael Hanselmann |
|
24 | 341ff8e9 | Michael Hanselmann | A query filter is always a list. The first item in the list is the operator
|
25 | 341ff8e9 | Michael Hanselmann | (e.g. C{[OP_AND, ...]}), while the other items depend on the operator. For
|
26 | 341ff8e9 | Michael Hanselmann | logic operators (e.g. L{OP_AND}, L{OP_OR}), they are subfilters whose results
|
27 | 341ff8e9 | Michael Hanselmann | are combined. Unary operators take exactly one other item (e.g. a subfilter for
|
28 | 341ff8e9 | Michael Hanselmann | L{OP_NOT} and a field name for L{OP_TRUE}). Binary operators take exactly two
|
29 | 341ff8e9 | Michael Hanselmann | operands, usually a field name and a value to compare against. Filters are
|
30 | 341ff8e9 | Michael Hanselmann | converted to callable functions by L{query._CompileFilter}.
|
31 | 341ff8e9 | Michael Hanselmann |
|
32 | 341ff8e9 | Michael Hanselmann | """
|
33 | a123dc19 | Michael Hanselmann | |
34 | 7578ab0a | Michael Hanselmann | import re |
35 | 3f2f55bb | Michael Hanselmann | import string # pylint: disable-msg=W0402 |
36 | 7578ab0a | Michael Hanselmann | |
37 | 7578ab0a | Michael Hanselmann | import pyparsing as pyp |
38 | 7578ab0a | Michael Hanselmann | |
39 | 7578ab0a | Michael Hanselmann | from ganeti import errors |
40 | 3f2f55bb | Michael Hanselmann | from ganeti import netutils |
41 | 7578ab0a | Michael Hanselmann | |
42 | 7578ab0a | Michael Hanselmann | |
43 | 341ff8e9 | Michael Hanselmann | # Logic operators with one or more operands, each of which is a filter on its
|
44 | 341ff8e9 | Michael Hanselmann | # own
|
45 | a123dc19 | Michael Hanselmann | OP_OR = "|"
|
46 | 88076fd1 | Michael Hanselmann | OP_AND = "&"
|
47 | 88076fd1 | Michael Hanselmann | |
48 | 88076fd1 | Michael Hanselmann | |
49 | 341ff8e9 | Michael Hanselmann | # Unary operators with exactly one operand
|
50 | 88076fd1 | Michael Hanselmann | OP_NOT = "!"
|
51 | 3b877f08 | Michael Hanselmann | OP_TRUE = "?"
|
52 | 88076fd1 | Michael Hanselmann | |
53 | 88076fd1 | Michael Hanselmann | |
54 | 341ff8e9 | Michael Hanselmann | # Binary operators with exactly two operands, the field name and an
|
55 | 341ff8e9 | Michael Hanselmann | # operator-specific value
|
56 | a123dc19 | Michael Hanselmann | OP_EQUAL = "="
|
57 | 88076fd1 | Michael Hanselmann | OP_NOT_EQUAL = "!="
|
58 | 88076fd1 | Michael Hanselmann | OP_REGEXP = "=~"
|
59 | 88076fd1 | Michael Hanselmann | OP_CONTAINS = "=[]"
|
60 | a123dc19 | Michael Hanselmann | |
61 | a123dc19 | Michael Hanselmann | |
62 | 3f2f55bb | Michael Hanselmann | #: Characters used for detecting user-written filters (see L{MaybeFilter})
|
63 | 3f2f55bb | Michael Hanselmann | FILTER_DETECTION_CHARS = frozenset("()=/!~" + string.whitespace) |
64 | 3f2f55bb | Michael Hanselmann | |
65 | 3f2f55bb | Michael Hanselmann | |
66 | 60cba7f8 | Michael Hanselmann | def MakeSimpleFilter(namefield, values): |
67 | 31554d0a | Michael Hanselmann | """Builds simple a filter.
|
68 | 60cba7f8 | Michael Hanselmann |
|
69 | 60cba7f8 | Michael Hanselmann | @param namefield: Name of field containing item name
|
70 | 60cba7f8 | Michael Hanselmann | @param values: List of names
|
71 | 60cba7f8 | Michael Hanselmann |
|
72 | 60cba7f8 | Michael Hanselmann | """
|
73 | 60cba7f8 | Michael Hanselmann | if values:
|
74 | 60cba7f8 | Michael Hanselmann | return [OP_OR] + [[OP_EQUAL, namefield, i] for i in values] |
75 | 60cba7f8 | Michael Hanselmann | |
76 | 60cba7f8 | Michael Hanselmann | return None |
77 | 7578ab0a | Michael Hanselmann | |
78 | 7578ab0a | Michael Hanselmann | |
79 | 7578ab0a | Michael Hanselmann | def _ConvertLogicOp(op): |
80 | 7578ab0a | Michael Hanselmann | """Creates parsing action function for logic operator.
|
81 | 7578ab0a | Michael Hanselmann |
|
82 | 7578ab0a | Michael Hanselmann | @type op: string
|
83 | 7578ab0a | Michael Hanselmann | @param op: Operator for data structure, e.g. L{OP_AND}
|
84 | 7578ab0a | Michael Hanselmann |
|
85 | 7578ab0a | Michael Hanselmann | """
|
86 | 7578ab0a | Michael Hanselmann | def fn(toks): |
87 | 7578ab0a | Michael Hanselmann | """Converts parser tokens to query operator structure.
|
88 | 7578ab0a | Michael Hanselmann |
|
89 | 7578ab0a | Michael Hanselmann | @rtype: list
|
90 | 7578ab0a | Michael Hanselmann | @return: Query operator structure, e.g. C{[OP_AND, ["=", "foo", "bar"]]}
|
91 | 7578ab0a | Michael Hanselmann |
|
92 | 7578ab0a | Michael Hanselmann | """
|
93 | 7578ab0a | Michael Hanselmann | operands = toks[0]
|
94 | 7578ab0a | Michael Hanselmann | |
95 | 7578ab0a | Michael Hanselmann | if len(operands) == 1: |
96 | 7578ab0a | Michael Hanselmann | return operands[0] |
97 | 7578ab0a | Michael Hanselmann | |
98 | 7578ab0a | Michael Hanselmann | # Build query operator structure
|
99 | 7578ab0a | Michael Hanselmann | return [[op] + operands.asList()]
|
100 | 7578ab0a | Michael Hanselmann | |
101 | 7578ab0a | Michael Hanselmann | return fn
|
102 | 7578ab0a | Michael Hanselmann | |
103 | 7578ab0a | Michael Hanselmann | |
104 | 7578ab0a | Michael Hanselmann | _KNOWN_REGEXP_DELIM = "/#^|"
|
105 | 7578ab0a | Michael Hanselmann | _KNOWN_REGEXP_FLAGS = frozenset("si") |
106 | 7578ab0a | Michael Hanselmann | |
107 | 7578ab0a | Michael Hanselmann | |
108 | 7578ab0a | Michael Hanselmann | def _ConvertRegexpValue(_, loc, toks): |
109 | 7578ab0a | Michael Hanselmann | """Regular expression value for condition.
|
110 | 7578ab0a | Michael Hanselmann |
|
111 | 7578ab0a | Michael Hanselmann | """
|
112 | 7578ab0a | Michael Hanselmann | (regexp, flags) = toks[0]
|
113 | 7578ab0a | Michael Hanselmann | |
114 | 7578ab0a | Michael Hanselmann | # Ensure only whitelisted flags are used
|
115 | 7578ab0a | Michael Hanselmann | unknown_flags = (frozenset(flags) - _KNOWN_REGEXP_FLAGS)
|
116 | 7578ab0a | Michael Hanselmann | if unknown_flags:
|
117 | 7578ab0a | Michael Hanselmann | raise pyp.ParseFatalException("Unknown regular expression flags: '%s'" % |
118 | 7578ab0a | Michael Hanselmann | "".join(unknown_flags), loc)
|
119 | 7578ab0a | Michael Hanselmann | |
120 | 7578ab0a | Michael Hanselmann | if flags:
|
121 | 7578ab0a | Michael Hanselmann | re_flags = "(?%s)" % "".join(sorted(flags)) |
122 | 7578ab0a | Michael Hanselmann | else:
|
123 | 7578ab0a | Michael Hanselmann | re_flags = ""
|
124 | 7578ab0a | Michael Hanselmann | |
125 | 7578ab0a | Michael Hanselmann | re_cond = re_flags + regexp |
126 | 7578ab0a | Michael Hanselmann | |
127 | 7578ab0a | Michael Hanselmann | # Test if valid
|
128 | 7578ab0a | Michael Hanselmann | try:
|
129 | 7578ab0a | Michael Hanselmann | re.compile(re_cond) |
130 | 7578ab0a | Michael Hanselmann | except re.error, err:
|
131 | 7578ab0a | Michael Hanselmann | raise pyp.ParseFatalException("Invalid regular expression (%s)" % err, loc) |
132 | 7578ab0a | Michael Hanselmann | |
133 | 7578ab0a | Michael Hanselmann | return [re_cond]
|
134 | 7578ab0a | Michael Hanselmann | |
135 | 7578ab0a | Michael Hanselmann | |
136 | 7578ab0a | Michael Hanselmann | def BuildFilterParser(): |
137 | 7578ab0a | Michael Hanselmann | """Builds a parser for query filter strings.
|
138 | 7578ab0a | Michael Hanselmann |
|
139 | 7578ab0a | Michael Hanselmann | @rtype: pyparsing.ParserElement
|
140 | 7578ab0a | Michael Hanselmann |
|
141 | 7578ab0a | Michael Hanselmann | """
|
142 | 7578ab0a | Michael Hanselmann | field_name = pyp.Word(pyp.alphas, pyp.alphanums + "_/.")
|
143 | 7578ab0a | Michael Hanselmann | |
144 | 7578ab0a | Michael Hanselmann | # Integer
|
145 | 7578ab0a | Michael Hanselmann | num_sign = pyp.Word("-+", exact=1) |
146 | 7578ab0a | Michael Hanselmann | number = pyp.Combine(pyp.Optional(num_sign) + pyp.Word(pyp.nums)) |
147 | 7578ab0a | Michael Hanselmann | number.setParseAction(lambda toks: int(toks[0])) |
148 | 7578ab0a | Michael Hanselmann | |
149 | 7578ab0a | Michael Hanselmann | # Right-hand-side value
|
150 | 7578ab0a | Michael Hanselmann | rval = (number | pyp.quotedString.setParseAction(pyp.removeQuotes)) |
151 | 7578ab0a | Michael Hanselmann | |
152 | 7578ab0a | Michael Hanselmann | # Boolean condition
|
153 | 7578ab0a | Michael Hanselmann | bool_cond = field_name.copy() |
154 | 7578ab0a | Michael Hanselmann | bool_cond.setParseAction(lambda (fname, ): [[OP_TRUE, fname]])
|
155 | 7578ab0a | Michael Hanselmann | |
156 | 7578ab0a | Michael Hanselmann | # Simple binary conditions
|
157 | 7578ab0a | Michael Hanselmann | binopstbl = { |
158 | 7578ab0a | Michael Hanselmann | "==": OP_EQUAL,
|
159 | 7578ab0a | Michael Hanselmann | "!=": OP_NOT_EQUAL,
|
160 | 7578ab0a | Michael Hanselmann | } |
161 | 7578ab0a | Michael Hanselmann | |
162 | 7578ab0a | Michael Hanselmann | binary_cond = (field_name + pyp.oneOf(binopstbl.keys()) + rval) |
163 | 7578ab0a | Michael Hanselmann | binary_cond.setParseAction(lambda (lhs, op, rhs): [[binopstbl[op], lhs, rhs]])
|
164 | 7578ab0a | Michael Hanselmann | |
165 | 7578ab0a | Michael Hanselmann | # "in" condition
|
166 | 7578ab0a | Michael Hanselmann | in_cond = (rval + pyp.Suppress("in") + field_name)
|
167 | 7578ab0a | Michael Hanselmann | in_cond.setParseAction(lambda (value, field): [[OP_CONTAINS, field, value]])
|
168 | 7578ab0a | Michael Hanselmann | |
169 | 7578ab0a | Michael Hanselmann | # "not in" condition
|
170 | 7578ab0a | Michael Hanselmann | not_in_cond = (rval + pyp.Suppress("not") + pyp.Suppress("in") + field_name) |
171 | 7578ab0a | Michael Hanselmann | not_in_cond.setParseAction(lambda (value, field): [[OP_NOT, [OP_CONTAINS,
|
172 | 7578ab0a | Michael Hanselmann | field, value]]]) |
173 | 7578ab0a | Michael Hanselmann | |
174 | 7578ab0a | Michael Hanselmann | # Regular expression, e.g. m/foobar/i
|
175 | 7578ab0a | Michael Hanselmann | regexp_val = pyp.Group(pyp.Optional("m").suppress() +
|
176 | 7578ab0a | Michael Hanselmann | pyp.MatchFirst([pyp.QuotedString(i, escChar="\\")
|
177 | 7578ab0a | Michael Hanselmann | for i in _KNOWN_REGEXP_DELIM]) + |
178 | 7578ab0a | Michael Hanselmann | pyp.Optional(pyp.Word(pyp.alphas), default=""))
|
179 | 7578ab0a | Michael Hanselmann | regexp_val.setParseAction(_ConvertRegexpValue) |
180 | 7578ab0a | Michael Hanselmann | regexp_cond = (field_name + pyp.Suppress("=~") + regexp_val)
|
181 | 7578ab0a | Michael Hanselmann | regexp_cond.setParseAction(lambda (field, value): [[OP_REGEXP, field, value]])
|
182 | 7578ab0a | Michael Hanselmann | |
183 | 7578ab0a | Michael Hanselmann | not_regexp_cond = (field_name + pyp.Suppress("!~") + regexp_val)
|
184 | 7578ab0a | Michael Hanselmann | not_regexp_cond.setParseAction(lambda (field, value):
|
185 | 7578ab0a | Michael Hanselmann | [[OP_NOT, [OP_REGEXP, field, value]]]) |
186 | 7578ab0a | Michael Hanselmann | |
187 | 7578ab0a | Michael Hanselmann | # All possible conditions
|
188 | 7578ab0a | Michael Hanselmann | condition = (binary_cond ^ bool_cond ^ |
189 | 7578ab0a | Michael Hanselmann | in_cond ^ not_in_cond ^ |
190 | 7578ab0a | Michael Hanselmann | regexp_cond ^ not_regexp_cond) |
191 | 7578ab0a | Michael Hanselmann | |
192 | 7578ab0a | Michael Hanselmann | # Associativity operators
|
193 | 7578ab0a | Michael Hanselmann | filter_expr = pyp.operatorPrecedence(condition, [ |
194 | 7578ab0a | Michael Hanselmann | (pyp.Keyword("not").suppress(), 1, pyp.opAssoc.RIGHT, |
195 | 7578ab0a | Michael Hanselmann | lambda toks: [[OP_NOT, toks[0][0]]]), |
196 | 7578ab0a | Michael Hanselmann | (pyp.Keyword("and").suppress(), 2, pyp.opAssoc.LEFT, |
197 | 7578ab0a | Michael Hanselmann | _ConvertLogicOp(OP_AND)), |
198 | 7578ab0a | Michael Hanselmann | (pyp.Keyword("or").suppress(), 2, pyp.opAssoc.LEFT, |
199 | 7578ab0a | Michael Hanselmann | _ConvertLogicOp(OP_OR)), |
200 | 7578ab0a | Michael Hanselmann | ]) |
201 | 7578ab0a | Michael Hanselmann | |
202 | 7578ab0a | Michael Hanselmann | parser = pyp.StringStart() + filter_expr + pyp.StringEnd() |
203 | 7578ab0a | Michael Hanselmann | parser.parseWithTabs() |
204 | 7578ab0a | Michael Hanselmann | |
205 | 7578ab0a | Michael Hanselmann | # Originally C{parser.validate} was called here, but there seems to be some
|
206 | 7578ab0a | Michael Hanselmann | # issue causing it to fail whenever the "not" operator is included above.
|
207 | 7578ab0a | Michael Hanselmann | |
208 | 7578ab0a | Michael Hanselmann | return parser
|
209 | 7578ab0a | Michael Hanselmann | |
210 | 7578ab0a | Michael Hanselmann | |
211 | 7578ab0a | Michael Hanselmann | def ParseFilter(text, parser=None): |
212 | 7578ab0a | Michael Hanselmann | """Parses a query filter.
|
213 | 7578ab0a | Michael Hanselmann |
|
214 | 7578ab0a | Michael Hanselmann | @type text: string
|
215 | 7578ab0a | Michael Hanselmann | @param text: Query filter
|
216 | 7578ab0a | Michael Hanselmann | @type parser: pyparsing.ParserElement
|
217 | 7578ab0a | Michael Hanselmann | @param parser: Pyparsing object
|
218 | 7578ab0a | Michael Hanselmann | @rtype: list
|
219 | 7578ab0a | Michael Hanselmann |
|
220 | 7578ab0a | Michael Hanselmann | """
|
221 | 7578ab0a | Michael Hanselmann | if parser is None: |
222 | 7578ab0a | Michael Hanselmann | parser = BuildFilterParser() |
223 | 7578ab0a | Michael Hanselmann | |
224 | 7578ab0a | Michael Hanselmann | try:
|
225 | 7578ab0a | Michael Hanselmann | return parser.parseString(text)[0] |
226 | 7578ab0a | Michael Hanselmann | except pyp.ParseBaseException, err:
|
227 | 7578ab0a | Michael Hanselmann | raise errors.QueryFilterParseError("Failed to parse query filter" |
228 | 7578ab0a | Michael Hanselmann | " '%s': %s" % (text, err), err)
|
229 | 3f2f55bb | Michael Hanselmann | |
230 | 3f2f55bb | Michael Hanselmann | |
231 | 3f2f55bb | Michael Hanselmann | def MaybeFilter(text): |
232 | 3f2f55bb | Michael Hanselmann | """Try to determine if a string is a filter or a name.
|
233 | 3f2f55bb | Michael Hanselmann |
|
234 | 3f2f55bb | Michael Hanselmann | If in doubt, this function treats a text as a name.
|
235 | 3f2f55bb | Michael Hanselmann |
|
236 | 3f2f55bb | Michael Hanselmann | @type text: string
|
237 | 3f2f55bb | Michael Hanselmann | @param text: String to be examined
|
238 | 3f2f55bb | Michael Hanselmann | @rtype: bool
|
239 | 3f2f55bb | Michael Hanselmann |
|
240 | 3f2f55bb | Michael Hanselmann | """
|
241 | 3f2f55bb | Michael Hanselmann | # Quick check for punctuation and whitespace
|
242 | 3f2f55bb | Michael Hanselmann | if frozenset(text) & FILTER_DETECTION_CHARS: |
243 | 3f2f55bb | Michael Hanselmann | return True |
244 | 3f2f55bb | Michael Hanselmann | |
245 | 3f2f55bb | Michael Hanselmann | try:
|
246 | 3f2f55bb | Michael Hanselmann | netutils.Hostname.GetNormalizedName(text) |
247 | 3f2f55bb | Michael Hanselmann | except errors.OpPrereqError:
|
248 | 3f2f55bb | Michael Hanselmann | # Not a valid hostname, treat as filter
|
249 | 3f2f55bb | Michael Hanselmann | return True |
250 | 3f2f55bb | Michael Hanselmann | |
251 | 3f2f55bb | Michael Hanselmann | # Most probably a name
|
252 | 3f2f55bb | Michael Hanselmann | return False |