def testGlob(self): self.assertEqual(qlang.MakeFilter(["*.site"], False), [qlang.OP_OR, [qlang.OP_REGEXP, "name", utils.DnsNameGlobPattern("*.site")]]) self.assertEqual(qlang.MakeFilter(["web?.example"], False), [qlang.OP_OR, [qlang.OP_REGEXP, "name", utils.DnsNameGlobPattern("web?.example")]]) self.assertEqual(qlang.MakeFilter(["*.a", "*.b", "?.c"], False), [qlang.OP_OR, [qlang.OP_REGEXP, "name", utils.DnsNameGlobPattern("*.a")], [qlang.OP_REGEXP, "name", utils.DnsNameGlobPattern("*.b")], [qlang.OP_REGEXP, "name", utils.DnsNameGlobPattern("?.c")]])
def _MakeFilterPart(namefield, text, isnumeric=False): """Generates filter for one argument. """ if isnumeric: try: number = int(text) except (TypeError, ValueError) as err: raise errors.OpPrereqError("Invalid job ID passed: %s" % str(err), errors.ECODE_INVAL) return [OP_EQUAL, namefield, number] elif _CheckGlobbing(text): return [OP_REGEXP, namefield, utils.DnsNameGlobPattern(text)] else: return [OP_EQUAL, namefield, text]
def testFilter(self): self.assertEqual(qlang.MakeFilter(["foo/bar"], False), [qlang.OP_TRUE, "foo/bar"]) self.assertEqual(qlang.MakeFilter(["foo=='bar'"], False), [qlang.OP_EQUAL, "foo", "bar"]) self.assertEqual(qlang.MakeFilter(["field=*'*.site'"], False), [qlang.OP_REGEXP, "field", utils.DnsNameGlobPattern("*.site")]) # Plain name parses as name filter, not boolean for name in ["node1", "n-o-d-e", "n_o_d_e", "node1.example.com", "node1.example.com."]: self.assertEqual(qlang.MakeFilter([name], False), [qlang.OP_OR, [qlang.OP_EQUAL, "name", name]]) # Invalid filters for i in ["foo==bar", "foo+=1"]: self.assertRaises(errors.QueryFilterParseError, qlang.MakeFilter, [i], False)
def BuildFilterParser(): """Builds a parser for query filter strings. @rtype: pyparsing.ParserElement """ field_name = pyp.Word(pyp.alphas, pyp.alphanums + "_/.") # Integer num_sign = pyp.Word("-+", exact=1) number = pyp.Combine(pyp.Optional(num_sign) + pyp.Word(pyp.nums)) number.setParseAction(lambda toks: int(toks[0])) quoted_string = pyp.quotedString.copy().setParseAction(pyp.removeQuotes) # Right-hand-side value rval = (number | quoted_string) # Boolean condition bool_cond = field_name.copy() bool_cond.setParseAction(lambda (fname, ): [[OP_TRUE, fname]]) # Simple binary conditions binopstbl = { "==": OP_EQUAL, "!=": OP_NOT_EQUAL, "<": OP_LT, "<=": OP_LE, ">": OP_GT, ">=": OP_GE, } binary_cond = (field_name + pyp.oneOf(binopstbl.keys()) + rval) binary_cond.setParseAction(lambda (lhs, op, rhs): [[binopstbl[op], lhs, rhs]]) # "in" condition in_cond = (rval + pyp.Suppress("in") + field_name) in_cond.setParseAction(lambda (value, field): [[OP_CONTAINS, field, value]]) # "not in" condition not_in_cond = (rval + pyp.Suppress("not") + pyp.Suppress("in") + field_name) not_in_cond.setParseAction( lambda (value, field): [[OP_NOT, [OP_CONTAINS, field, value]]]) # Regular expression, e.g. m/foobar/i regexp_val = pyp.Group( pyp.Optional("m").suppress() + pyp.MatchFirst( [pyp.QuotedString(i, escChar="\\") for i in _KNOWN_REGEXP_DELIM]) + pyp.Optional(pyp.Word(pyp.alphas), default="")) regexp_val.setParseAction(_ConvertRegexpValue) regexp_cond = (field_name + pyp.Suppress("=~") + regexp_val) regexp_cond.setParseAction(lambda (field, value): [[OP_REGEXP, field, value]]) not_regexp_cond = (field_name + pyp.Suppress("!~") + regexp_val) not_regexp_cond.setParseAction( lambda (field, value): [[OP_NOT, [OP_REGEXP, field, value]]]) # Globbing, e.g. name =* "*.site" glob_cond = (field_name + pyp.Suppress("=*") + quoted_string) glob_cond.setParseAction( lambda (field, value): [[OP_REGEXP, field, utils.DnsNameGlobPattern(value)]]) not_glob_cond = (field_name + pyp.Suppress("!*") + quoted_string) not_glob_cond.setParseAction( lambda (field, value): [[OP_NOT, [OP_REGEXP, field, utils.DnsNameGlobPattern(value)]]]) # All possible conditions condition = (binary_cond ^ bool_cond ^ in_cond ^ not_in_cond ^ regexp_cond ^ not_regexp_cond ^ glob_cond ^ not_glob_cond) # Associativity operators filter_expr = pyp.operatorPrecedence(condition, [ (pyp.Keyword("not").suppress(), 1, pyp.opAssoc.RIGHT, lambda toks: [[OP_NOT, toks[0][0]]]), (pyp.Keyword("and").suppress(), 2, pyp.opAssoc.LEFT, _ConvertLogicOp(OP_AND)), (pyp.Keyword("or").suppress(), 2, pyp.opAssoc.LEFT, _ConvertLogicOp(OP_OR)), ]) parser = pyp.StringStart() + filter_expr + pyp.StringEnd() parser.parseWithTabs() # Originally C{parser.validate} was called here, but there seems to be some # issue causing it to fail whenever the "not" operator is included above. return parser
return bool(frozenset(text) & GLOB_DETECTION_CHARS) def _MakeFilterPart(namefield, text, isnumeric=False): """Generates filter for one argument. """ if isnumeric: try: number = int(text) except (TypeError, ValueError), err: raise errors.OpPrereqError("Invalid job ID passed: %s" % str(err), errors.ECODE_INVAL) return [OP_EQUAL, namefield, number] elif _CheckGlobbing(text): return [OP_REGEXP, namefield, utils.DnsNameGlobPattern(text)] else: return [OP_EQUAL, namefield, text] def MakeFilter(args, force_filter, namefield=None, isnumeric=False): """Try to make a filter from arguments to a command. If the name could be a filter it is parsed as such. If it's just a globbing pattern, e.g. "*.site", such a filter is constructed. As a last resort the names are treated just as a plain name filter. @type args: list of string @param args: Arguments to command @type force_filter: bool @param force_filter: Whether to force treatment as a full-fledged filter
def test(self): self._Test("name==\"foobar\"", [qlang.OP_EQUAL, "name", "foobar"]) self._Test("name=='foobar'", [qlang.OP_EQUAL, "name", "foobar"]) # Legacy "=" self._Test("name=\"foobar\"", [qlang.OP_EQUAL, "name", "foobar"]) self._Test("name='foobar'", [qlang.OP_EQUAL, "name", "foobar"]) self._Test("valA==1 and valB==2 or valC==3", [ qlang.OP_OR, [ qlang.OP_AND, [qlang.OP_EQUAL, "valA", 1], [qlang.OP_EQUAL, "valB", 2] ], [qlang.OP_EQUAL, "valC", 3] ]) self._Test(("(name\n==\"foobar\") and (xyz==\"va)ue\" and k == 256 or" " x ==\t\"y\"\n) and mc"), [ qlang.OP_AND, [qlang.OP_EQUAL, "name", "foobar"], [ qlang.OP_OR, [ qlang.OP_AND, [qlang.OP_EQUAL, "xyz", "va)ue"], [qlang.OP_EQUAL, "k", 256] ], [qlang.OP_EQUAL, "x", "y"] ], [qlang.OP_TRUE, "mc"] ]) self._Test("(xyz==\"v\" or k == 256 and x == \"y\")", [ qlang.OP_OR, [qlang.OP_EQUAL, "xyz", "v"], [ qlang.OP_AND, [qlang.OP_EQUAL, "k", 256], [qlang.OP_EQUAL, "x", "y"] ] ]) self._Test("valA==1 and valB==2 and valC==3", [ qlang.OP_AND, [qlang.OP_EQUAL, "valA", 1], [qlang.OP_EQUAL, "valB", 2], [qlang.OP_EQUAL, "valC", 3] ]) self._Test( "master or field", [qlang.OP_OR, [qlang.OP_TRUE, "master"], [qlang.OP_TRUE, "field"]]) self._Test("mem == 128", [qlang.OP_EQUAL, "mem", 128]) self._Test("negfield != -1", [qlang.OP_NOT_EQUAL, "negfield", -1]) self._Test("master", [qlang.OP_TRUE, "master"], expect_filter=False) self._Test("not master", [qlang.OP_NOT, [qlang.OP_TRUE, "master"]]) for op in ["not", "and", "or"]: self._Test("%sxyz" % op, [qlang.OP_TRUE, "%sxyz" % op], expect_filter=False) self._Test( "not %sxyz" % op, [qlang.OP_NOT, [qlang.OP_TRUE, "%sxyz" % op]]) self._Test( " not \t%sfoo" % op, [qlang.OP_NOT, [qlang.OP_TRUE, "%sfoo" % op]]) self._Test("%sname =~ m/abc/" % op, [qlang.OP_REGEXP, "%sname" % op, "abc"]) self._Test("master and not other", [ qlang.OP_AND, [qlang.OP_TRUE, "master"], [qlang.OP_NOT, [qlang.OP_TRUE, "other"]] ]) self._Test("not (master or other == 4)", [ qlang.OP_NOT, [ qlang.OP_OR, [qlang.OP_TRUE, "master"], [qlang.OP_EQUAL, "other", 4] ] ]) self._Test("some==\"val\\\"ue\"", [qlang.OP_EQUAL, "some", "val\\\"ue"]) self._Test("123 in ips", [qlang.OP_CONTAINS, "ips", 123]) self._Test("99 not in ips", [qlang.OP_NOT, [qlang.OP_CONTAINS, "ips", 99]]) self._Test("\"a\" in valA and \"b\" not in valB", [ qlang.OP_AND, [qlang.OP_CONTAINS, "valA", "a"], [qlang.OP_NOT, [qlang.OP_CONTAINS, "valB", "b"]] ]) self._Test("name =~ m/test/", [qlang.OP_REGEXP, "name", "test"]) self._Test("name =~ m/^node.*example.com$/i", [qlang.OP_REGEXP, "name", "(?i)^node.*example.com$"]) self._Test( "(name =~ m/^node.*example.com$/s and master) or pip =~ |^3.*|", [ qlang.OP_OR, [ qlang.OP_AND, [qlang.OP_REGEXP, "name", "(?s)^node.*example.com$"], [qlang.OP_TRUE, "master"] ], [qlang.OP_REGEXP, "pip", "^3.*"] ]) for flags in ["si", "is", "ssss", "iiiisiii"]: self._Test( "name =~ m/gi/%s" % flags, [qlang.OP_REGEXP, "name", "(?%s)gi" % "".join(sorted(flags))]) for i in qlang._KNOWN_REGEXP_DELIM: self._Test("name =~ m%stest%s" % (i, i), [qlang.OP_REGEXP, "name", "test"]) self._Test("name !~ m%stest%s" % (i, i), [qlang.OP_NOT, [qlang.OP_REGEXP, "name", "test"]]) self._Test("not\tname =~ m%stest%s" % (i, i), [qlang.OP_NOT, [qlang.OP_REGEXP, "name", "test"]]) self._Test("notname =~ m%stest%s" % (i, i), [qlang.OP_REGEXP, "notname", "test"]) self._Test( "name =* '*.site'", [qlang.OP_REGEXP, "name", utils.DnsNameGlobPattern("*.site")]) self._Test("field !* '*.example.*'", [ qlang.OP_NOT, [ qlang.OP_REGEXP, "field", utils.DnsNameGlobPattern("*.example.*") ] ]) self._Test("ctime < 1234", [qlang.OP_LT, "ctime", 1234]) self._Test("ctime > 1234", [qlang.OP_GT, "ctime", 1234]) self._Test("mtime <= 9999", [qlang.OP_LE, "mtime", 9999]) self._Test("mtime >= 9999", [qlang.OP_GE, "mtime", 9999])
def _Test(self, pattern): re_pat = re.compile(utils.DnsNameGlobPattern(pattern)) return [n for n in self.names if re_pat.match(n)]
def _Test(self, pattern): re_pat = utils.DnsNameGlobPattern(pattern) return filter(re.compile(re_pat).match, self.names)