Statistics
| Branch: | Tag: | Revision:

root / lib / qlang.py @ 31d3b918

History | View | Annotate | Download (9.5 kB)

1 a123dc19 Michael Hanselmann
#
2 a123dc19 Michael Hanselmann
#
3 a123dc19 Michael Hanselmann
4 2cfbc784 Iustin Pop
# Copyright (C) 2010, 2011, 2012 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 f8638e28 Michael Hanselmann
import logging
36 7578ab0a Michael Hanselmann
37 7578ab0a Michael Hanselmann
import pyparsing as pyp
38 7578ab0a Michael Hanselmann
39 67620dff Jose A. Lopes
from ganeti import constants
40 7578ab0a Michael Hanselmann
from ganeti import errors
41 16629d10 Michael Hanselmann
from ganeti import utils
42 f8638e28 Michael Hanselmann
from ganeti import compat
43 7578ab0a Michael Hanselmann
44 7578ab0a Michael Hanselmann
45 67620dff Jose A. Lopes
OP_OR = constants.QLANG_OP_OR
46 67620dff Jose A. Lopes
OP_AND = constants.QLANG_OP_AND
47 67620dff Jose A. Lopes
OP_NOT = constants.QLANG_OP_NOT
48 67620dff Jose A. Lopes
OP_TRUE = constants.QLANG_OP_TRUE
49 67620dff Jose A. Lopes
OP_EQUAL = constants.QLANG_OP_EQUAL
50 67620dff Jose A. Lopes
OP_NOT_EQUAL = constants.QLANG_OP_NOT_EQUAL
51 67620dff Jose A. Lopes
OP_LT = constants.QLANG_OP_LT
52 67620dff Jose A. Lopes
OP_LE = constants.QLANG_OP_LE
53 67620dff Jose A. Lopes
OP_GT = constants.QLANG_OP_GT
54 67620dff Jose A. Lopes
OP_GE = constants.QLANG_OP_GE
55 67620dff Jose A. Lopes
OP_REGEXP = constants.QLANG_OP_REGEXP
56 67620dff Jose A. Lopes
OP_CONTAINS = constants.QLANG_OP_CONTAINS
57 67620dff Jose A. Lopes
FILTER_DETECTION_CHARS = constants.QLANG_FILTER_DETECTION_CHARS
58 67620dff Jose A. Lopes
GLOB_DETECTION_CHARS = constants.QLANG_GLOB_DETECTION_CHARS
59 3f2f55bb Michael Hanselmann
60 3f2f55bb Michael Hanselmann
61 60cba7f8 Michael Hanselmann
def MakeSimpleFilter(namefield, values):
62 31554d0a Michael Hanselmann
  """Builds simple a filter.
63 60cba7f8 Michael Hanselmann

64 60cba7f8 Michael Hanselmann
  @param namefield: Name of field containing item name
65 60cba7f8 Michael Hanselmann
  @param values: List of names
66 60cba7f8 Michael Hanselmann

67 60cba7f8 Michael Hanselmann
  """
68 60cba7f8 Michael Hanselmann
  if values:
69 60cba7f8 Michael Hanselmann
    return [OP_OR] + [[OP_EQUAL, namefield, i] for i in values]
70 60cba7f8 Michael Hanselmann
71 60cba7f8 Michael Hanselmann
  return None
72 7578ab0a Michael Hanselmann
73 7578ab0a Michael Hanselmann
74 7578ab0a Michael Hanselmann
def _ConvertLogicOp(op):
75 7578ab0a Michael Hanselmann
  """Creates parsing action function for logic operator.
76 7578ab0a Michael Hanselmann

77 7578ab0a Michael Hanselmann
  @type op: string
78 7578ab0a Michael Hanselmann
  @param op: Operator for data structure, e.g. L{OP_AND}
79 7578ab0a Michael Hanselmann

80 7578ab0a Michael Hanselmann
  """
81 7578ab0a Michael Hanselmann
  def fn(toks):
82 7578ab0a Michael Hanselmann
    """Converts parser tokens to query operator structure.
83 7578ab0a Michael Hanselmann

84 7578ab0a Michael Hanselmann
    @rtype: list
85 7578ab0a Michael Hanselmann
    @return: Query operator structure, e.g. C{[OP_AND, ["=", "foo", "bar"]]}
86 7578ab0a Michael Hanselmann

87 7578ab0a Michael Hanselmann
    """
88 7578ab0a Michael Hanselmann
    operands = toks[0]
89 7578ab0a Michael Hanselmann
90 7578ab0a Michael Hanselmann
    if len(operands) == 1:
91 7578ab0a Michael Hanselmann
      return operands[0]
92 7578ab0a Michael Hanselmann
93 7578ab0a Michael Hanselmann
    # Build query operator structure
94 7578ab0a Michael Hanselmann
    return [[op] + operands.asList()]
95 7578ab0a Michael Hanselmann
96 7578ab0a Michael Hanselmann
  return fn
97 7578ab0a Michael Hanselmann
98 7578ab0a Michael Hanselmann
99 7578ab0a Michael Hanselmann
_KNOWN_REGEXP_DELIM = "/#^|"
100 7578ab0a Michael Hanselmann
_KNOWN_REGEXP_FLAGS = frozenset("si")
101 7578ab0a Michael Hanselmann
102 7578ab0a Michael Hanselmann
103 7578ab0a Michael Hanselmann
def _ConvertRegexpValue(_, loc, toks):
104 7578ab0a Michael Hanselmann
  """Regular expression value for condition.
105 7578ab0a Michael Hanselmann

106 7578ab0a Michael Hanselmann
  """
107 7578ab0a Michael Hanselmann
  (regexp, flags) = toks[0]
108 7578ab0a Michael Hanselmann
109 7578ab0a Michael Hanselmann
  # Ensure only whitelisted flags are used
110 7578ab0a Michael Hanselmann
  unknown_flags = (frozenset(flags) - _KNOWN_REGEXP_FLAGS)
111 7578ab0a Michael Hanselmann
  if unknown_flags:
112 7578ab0a Michael Hanselmann
    raise pyp.ParseFatalException("Unknown regular expression flags: '%s'" %
113 7578ab0a Michael Hanselmann
                                  "".join(unknown_flags), loc)
114 7578ab0a Michael Hanselmann
115 7578ab0a Michael Hanselmann
  if flags:
116 7578ab0a Michael Hanselmann
    re_flags = "(?%s)" % "".join(sorted(flags))
117 7578ab0a Michael Hanselmann
  else:
118 7578ab0a Michael Hanselmann
    re_flags = ""
119 7578ab0a Michael Hanselmann
120 7578ab0a Michael Hanselmann
  re_cond = re_flags + regexp
121 7578ab0a Michael Hanselmann
122 7578ab0a Michael Hanselmann
  # Test if valid
123 7578ab0a Michael Hanselmann
  try:
124 7578ab0a Michael Hanselmann
    re.compile(re_cond)
125 7578ab0a Michael Hanselmann
  except re.error, err:
126 7578ab0a Michael Hanselmann
    raise pyp.ParseFatalException("Invalid regular expression (%s)" % err, loc)
127 7578ab0a Michael Hanselmann
128 7578ab0a Michael Hanselmann
  return [re_cond]
129 7578ab0a Michael Hanselmann
130 7578ab0a Michael Hanselmann
131 7578ab0a Michael Hanselmann
def BuildFilterParser():
132 7578ab0a Michael Hanselmann
  """Builds a parser for query filter strings.
133 7578ab0a Michael Hanselmann

134 7578ab0a Michael Hanselmann
  @rtype: pyparsing.ParserElement
135 7578ab0a Michael Hanselmann

136 7578ab0a Michael Hanselmann
  """
137 7578ab0a Michael Hanselmann
  field_name = pyp.Word(pyp.alphas, pyp.alphanums + "_/.")
138 7578ab0a Michael Hanselmann
139 7578ab0a Michael Hanselmann
  # Integer
140 7578ab0a Michael Hanselmann
  num_sign = pyp.Word("-+", exact=1)
141 7578ab0a Michael Hanselmann
  number = pyp.Combine(pyp.Optional(num_sign) + pyp.Word(pyp.nums))
142 7578ab0a Michael Hanselmann
  number.setParseAction(lambda toks: int(toks[0]))
143 7578ab0a Michael Hanselmann
144 16629d10 Michael Hanselmann
  quoted_string = pyp.quotedString.copy().setParseAction(pyp.removeQuotes)
145 16629d10 Michael Hanselmann
146 7578ab0a Michael Hanselmann
  # Right-hand-side value
147 16629d10 Michael Hanselmann
  rval = (number | quoted_string)
148 7578ab0a Michael Hanselmann
149 7578ab0a Michael Hanselmann
  # Boolean condition
150 7578ab0a Michael Hanselmann
  bool_cond = field_name.copy()
151 7578ab0a Michael Hanselmann
  bool_cond.setParseAction(lambda (fname, ): [[OP_TRUE, fname]])
152 7578ab0a Michael Hanselmann
153 7578ab0a Michael Hanselmann
  # Simple binary conditions
154 7578ab0a Michael Hanselmann
  binopstbl = {
155 7578ab0a Michael Hanselmann
    "==": OP_EQUAL,
156 7578ab0a Michael Hanselmann
    "!=": OP_NOT_EQUAL,
157 ad48eacc Michael Hanselmann
    "<": OP_LT,
158 ad48eacc Michael Hanselmann
    "<=": OP_LE,
159 ad48eacc Michael Hanselmann
    ">": OP_GT,
160 ad48eacc Michael Hanselmann
    ">=": OP_GE,
161 7578ab0a Michael Hanselmann
    }
162 7578ab0a Michael Hanselmann
163 7578ab0a Michael Hanselmann
  binary_cond = (field_name + pyp.oneOf(binopstbl.keys()) + rval)
164 7578ab0a Michael Hanselmann
  binary_cond.setParseAction(lambda (lhs, op, rhs): [[binopstbl[op], lhs, rhs]])
165 7578ab0a Michael Hanselmann
166 7578ab0a Michael Hanselmann
  # "in" condition
167 7578ab0a Michael Hanselmann
  in_cond = (rval + pyp.Suppress("in") + field_name)
168 7578ab0a Michael Hanselmann
  in_cond.setParseAction(lambda (value, field): [[OP_CONTAINS, field, value]])
169 7578ab0a Michael Hanselmann
170 7578ab0a Michael Hanselmann
  # "not in" condition
171 7578ab0a Michael Hanselmann
  not_in_cond = (rval + pyp.Suppress("not") + pyp.Suppress("in") + field_name)
172 7578ab0a Michael Hanselmann
  not_in_cond.setParseAction(lambda (value, field): [[OP_NOT, [OP_CONTAINS,
173 7578ab0a Michael Hanselmann
                                                               field, value]]])
174 7578ab0a Michael Hanselmann
175 7578ab0a Michael Hanselmann
  # Regular expression, e.g. m/foobar/i
176 7578ab0a Michael Hanselmann
  regexp_val = pyp.Group(pyp.Optional("m").suppress() +
177 7578ab0a Michael Hanselmann
                         pyp.MatchFirst([pyp.QuotedString(i, escChar="\\")
178 7578ab0a Michael Hanselmann
                                         for i in _KNOWN_REGEXP_DELIM]) +
179 7578ab0a Michael Hanselmann
                         pyp.Optional(pyp.Word(pyp.alphas), default=""))
180 7578ab0a Michael Hanselmann
  regexp_val.setParseAction(_ConvertRegexpValue)
181 7578ab0a Michael Hanselmann
  regexp_cond = (field_name + pyp.Suppress("=~") + regexp_val)
182 7578ab0a Michael Hanselmann
  regexp_cond.setParseAction(lambda (field, value): [[OP_REGEXP, field, value]])
183 7578ab0a Michael Hanselmann
184 7578ab0a Michael Hanselmann
  not_regexp_cond = (field_name + pyp.Suppress("!~") + regexp_val)
185 7578ab0a Michael Hanselmann
  not_regexp_cond.setParseAction(lambda (field, value):
186 7578ab0a Michael Hanselmann
                                 [[OP_NOT, [OP_REGEXP, field, value]]])
187 7578ab0a Michael Hanselmann
188 16629d10 Michael Hanselmann
  # Globbing, e.g. name =* "*.site"
189 16629d10 Michael Hanselmann
  glob_cond = (field_name + pyp.Suppress("=*") + quoted_string)
190 16629d10 Michael Hanselmann
  glob_cond.setParseAction(lambda (field, value):
191 16629d10 Michael Hanselmann
                           [[OP_REGEXP, field,
192 16629d10 Michael Hanselmann
                             utils.DnsNameGlobPattern(value)]])
193 16629d10 Michael Hanselmann
194 16629d10 Michael Hanselmann
  not_glob_cond = (field_name + pyp.Suppress("!*") + quoted_string)
195 16629d10 Michael Hanselmann
  not_glob_cond.setParseAction(lambda (field, value):
196 16629d10 Michael Hanselmann
                               [[OP_NOT, [OP_REGEXP, field,
197 16629d10 Michael Hanselmann
                                          utils.DnsNameGlobPattern(value)]]])
198 16629d10 Michael Hanselmann
199 7578ab0a Michael Hanselmann
  # All possible conditions
200 7578ab0a Michael Hanselmann
  condition = (binary_cond ^ bool_cond ^
201 7578ab0a Michael Hanselmann
               in_cond ^ not_in_cond ^
202 16629d10 Michael Hanselmann
               regexp_cond ^ not_regexp_cond ^
203 16629d10 Michael Hanselmann
               glob_cond ^ not_glob_cond)
204 7578ab0a Michael Hanselmann
205 7578ab0a Michael Hanselmann
  # Associativity operators
206 7578ab0a Michael Hanselmann
  filter_expr = pyp.operatorPrecedence(condition, [
207 7578ab0a Michael Hanselmann
    (pyp.Keyword("not").suppress(), 1, pyp.opAssoc.RIGHT,
208 7578ab0a Michael Hanselmann
     lambda toks: [[OP_NOT, toks[0][0]]]),
209 7578ab0a Michael Hanselmann
    (pyp.Keyword("and").suppress(), 2, pyp.opAssoc.LEFT,
210 7578ab0a Michael Hanselmann
     _ConvertLogicOp(OP_AND)),
211 7578ab0a Michael Hanselmann
    (pyp.Keyword("or").suppress(), 2, pyp.opAssoc.LEFT,
212 7578ab0a Michael Hanselmann
     _ConvertLogicOp(OP_OR)),
213 7578ab0a Michael Hanselmann
    ])
214 7578ab0a Michael Hanselmann
215 7578ab0a Michael Hanselmann
  parser = pyp.StringStart() + filter_expr + pyp.StringEnd()
216 7578ab0a Michael Hanselmann
  parser.parseWithTabs()
217 7578ab0a Michael Hanselmann
218 7578ab0a Michael Hanselmann
  # Originally C{parser.validate} was called here, but there seems to be some
219 7578ab0a Michael Hanselmann
  # issue causing it to fail whenever the "not" operator is included above.
220 7578ab0a Michael Hanselmann
221 7578ab0a Michael Hanselmann
  return parser
222 7578ab0a Michael Hanselmann
223 7578ab0a Michael Hanselmann
224 7578ab0a Michael Hanselmann
def ParseFilter(text, parser=None):
225 7578ab0a Michael Hanselmann
  """Parses a query filter.
226 7578ab0a Michael Hanselmann

227 7578ab0a Michael Hanselmann
  @type text: string
228 7578ab0a Michael Hanselmann
  @param text: Query filter
229 7578ab0a Michael Hanselmann
  @type parser: pyparsing.ParserElement
230 7578ab0a Michael Hanselmann
  @param parser: Pyparsing object
231 7578ab0a Michael Hanselmann
  @rtype: list
232 7578ab0a Michael Hanselmann

233 7578ab0a Michael Hanselmann
  """
234 f8638e28 Michael Hanselmann
  logging.debug("Parsing as query filter: %s", text)
235 f8638e28 Michael Hanselmann
236 7578ab0a Michael Hanselmann
  if parser is None:
237 7578ab0a Michael Hanselmann
    parser = BuildFilterParser()
238 7578ab0a Michael Hanselmann
239 7578ab0a Michael Hanselmann
  try:
240 7578ab0a Michael Hanselmann
    return parser.parseString(text)[0]
241 7578ab0a Michael Hanselmann
  except pyp.ParseBaseException, err:
242 7578ab0a Michael Hanselmann
    raise errors.QueryFilterParseError("Failed to parse query filter"
243 7578ab0a Michael Hanselmann
                                       " '%s': %s" % (text, err), err)
244 3f2f55bb Michael Hanselmann
245 3f2f55bb Michael Hanselmann
246 f8638e28 Michael Hanselmann
def _CheckFilter(text):
247 f8638e28 Michael Hanselmann
  """CHecks if a string could be a filter.
248 f8638e28 Michael Hanselmann

249 f8638e28 Michael Hanselmann
  @rtype: bool
250 f8638e28 Michael Hanselmann

251 f8638e28 Michael Hanselmann
  """
252 f8638e28 Michael Hanselmann
  return bool(frozenset(text) & FILTER_DETECTION_CHARS)
253 f8638e28 Michael Hanselmann
254 f8638e28 Michael Hanselmann
255 f8638e28 Michael Hanselmann
def _CheckGlobbing(text):
256 f8638e28 Michael Hanselmann
  """Checks if a string could be a globbing pattern.
257 f8638e28 Michael Hanselmann

258 f8638e28 Michael Hanselmann
  @rtype: bool
259 f8638e28 Michael Hanselmann

260 f8638e28 Michael Hanselmann
  """
261 f8638e28 Michael Hanselmann
  return bool(frozenset(text) & GLOB_DETECTION_CHARS)
262 f8638e28 Michael Hanselmann
263 f8638e28 Michael Hanselmann
264 6f287cf3 Iustin Pop
def _MakeFilterPart(namefield, text, isnumeric=False):
265 f8638e28 Michael Hanselmann
  """Generates filter for one argument.
266 f8638e28 Michael Hanselmann

267 f8638e28 Michael Hanselmann
  """
268 6f287cf3 Iustin Pop
  if isnumeric:
269 6f287cf3 Iustin Pop
    try:
270 6f287cf3 Iustin Pop
      number = int(text)
271 6f287cf3 Iustin Pop
    except (TypeError, ValueError), err:
272 76b62028 Iustin Pop
      raise errors.OpPrereqError("Invalid job ID passed: %s" % str(err),
273 6f287cf3 Iustin Pop
                                 errors.ECODE_INVAL)
274 6f287cf3 Iustin Pop
    return [OP_EQUAL, namefield, number]
275 6f287cf3 Iustin Pop
  elif _CheckGlobbing(text):
276 f8638e28 Michael Hanselmann
    return [OP_REGEXP, namefield, utils.DnsNameGlobPattern(text)]
277 f8638e28 Michael Hanselmann
  else:
278 f8638e28 Michael Hanselmann
    return [OP_EQUAL, namefield, text]
279 f8638e28 Michael Hanselmann
280 f8638e28 Michael Hanselmann
281 6f287cf3 Iustin Pop
def MakeFilter(args, force_filter, namefield=None, isnumeric=False):
282 f8638e28 Michael Hanselmann
  """Try to make a filter from arguments to a command.
283 f8638e28 Michael Hanselmann

284 f8638e28 Michael Hanselmann
  If the name could be a filter it is parsed as such. If it's just a globbing
285 f8638e28 Michael Hanselmann
  pattern, e.g. "*.site", such a filter is constructed. As a last resort the
286 f8638e28 Michael Hanselmann
  names are treated just as a plain name filter.
287 f8638e28 Michael Hanselmann

288 f8638e28 Michael Hanselmann
  @type args: list of string
289 f8638e28 Michael Hanselmann
  @param args: Arguments to command
290 f8638e28 Michael Hanselmann
  @type force_filter: bool
291 f8638e28 Michael Hanselmann
  @param force_filter: Whether to force treatment as a full-fledged filter
292 03ec545a Michael Hanselmann
  @type namefield: string
293 03ec545a Michael Hanselmann
  @param namefield: Name of field to use for simple filters (use L{None} for
294 03ec545a Michael Hanselmann
    a default of "name")
295 6f287cf3 Iustin Pop
  @type isnumeric: bool
296 6f287cf3 Iustin Pop
  @param isnumeric: Whether the namefield type is numeric, as opposed to
297 6f287cf3 Iustin Pop
    the default string type; this influences how the filter is built
298 f8638e28 Michael Hanselmann
  @rtype: list
299 f8638e28 Michael Hanselmann
  @return: Query filter
300 f8638e28 Michael Hanselmann

301 f8638e28 Michael Hanselmann
  """
302 03ec545a Michael Hanselmann
  if namefield is None:
303 03ec545a Michael Hanselmann
    namefield = "name"
304 03ec545a Michael Hanselmann
305 f8638e28 Michael Hanselmann
  if (force_filter or
306 f8638e28 Michael Hanselmann
      (args and len(args) == 1 and _CheckFilter(args[0]))):
307 f8638e28 Michael Hanselmann
    try:
308 f8638e28 Michael Hanselmann
      (filter_text, ) = args
309 f8638e28 Michael Hanselmann
    except (TypeError, ValueError):
310 f8638e28 Michael Hanselmann
      raise errors.OpPrereqError("Exactly one argument must be given as a"
311 2cfbc784 Iustin Pop
                                 " filter", errors.ECODE_INVAL)
312 f8638e28 Michael Hanselmann
313 f8638e28 Michael Hanselmann
    result = ParseFilter(filter_text)
314 f8638e28 Michael Hanselmann
  elif args:
315 6f287cf3 Iustin Pop
    result = [OP_OR] + map(compat.partial(_MakeFilterPart, namefield,
316 6f287cf3 Iustin Pop
                                          isnumeric=isnumeric), args)
317 f8638e28 Michael Hanselmann
  else:
318 f8638e28 Michael Hanselmann
    result = None
319 f8638e28 Michael Hanselmann
320 f8638e28 Michael Hanselmann
  return result