Merge "Fix the -q/--query in threshold alarm creation"
This commit is contained in:
commit
03e7a45e5e
@ -125,7 +125,7 @@ class CliAlarmCreateTest(testtools.TestCase):
|
|||||||
'--comparison-operator', 'le',
|
'--comparison-operator', 'le',
|
||||||
'--threshold', '80',
|
'--threshold', '80',
|
||||||
'--event-type', 'event',
|
'--event-type', 'event',
|
||||||
'--query', '{}',
|
'--query', 'resource=fake-resource-id',
|
||||||
'--granularity', '60',
|
'--granularity', '60',
|
||||||
'--aggregation-method', 'last',
|
'--aggregation-method', 'last',
|
||||||
'--metric', 'cpu',
|
'--metric', 'cpu',
|
||||||
@ -158,11 +158,17 @@ class CliAlarmCreateTest(testtools.TestCase):
|
|||||||
'statistic': 'max',
|
'statistic': 'max',
|
||||||
'comparison_operator': 'le',
|
'comparison_operator': 'le',
|
||||||
'threshold': 80.0,
|
'threshold': 80.0,
|
||||||
'query': '{}'
|
'query': [{'field': 'resource',
|
||||||
|
'op': 'eq',
|
||||||
|
'type': '',
|
||||||
|
'value': 'fake-resource-id'}]
|
||||||
},
|
},
|
||||||
'event_rule': {
|
'event_rule': {
|
||||||
'event_type': 'event',
|
'event_type': 'event',
|
||||||
'query': '{}'
|
'query': [{'field': 'resource',
|
||||||
|
'op': 'eq',
|
||||||
|
'type': '',
|
||||||
|
'value': 'fake-resource-id'}]
|
||||||
},
|
},
|
||||||
'gnocchi_resources_threshold_rule': {
|
'gnocchi_resources_threshold_rule': {
|
||||||
'granularity': '60',
|
'granularity': '60',
|
||||||
@ -188,7 +194,10 @@ class CliAlarmCreateTest(testtools.TestCase):
|
|||||||
'evaluation_periods': 60,
|
'evaluation_periods': 60,
|
||||||
'comparison_operator': 'le',
|
'comparison_operator': 'le',
|
||||||
'threshold': 80.0,
|
'threshold': 80.0,
|
||||||
'query': '{}',
|
'query': [{'field': 'resource',
|
||||||
|
'op': 'eq',
|
||||||
|
'type': '',
|
||||||
|
'value': 'fake-resource-id'}],
|
||||||
'resource_type': 'generic'
|
'resource_type': 'generic'
|
||||||
},
|
},
|
||||||
'composite_rule': None,
|
'composite_rule': None,
|
||||||
|
@ -81,3 +81,13 @@ class SearchQueryBuilderTest(base.BaseTestCase):
|
|||||||
]},
|
]},
|
||||||
{"=": {"foo": "quote"}},
|
{"=": {"foo": "quote"}},
|
||||||
]})
|
]})
|
||||||
|
|
||||||
|
|
||||||
|
class CliQueryToArray(base.BaseTestCase):
|
||||||
|
def test_cli_query_to_arrary(self):
|
||||||
|
cli_query = "this<=34;that=string::foo"
|
||||||
|
ret_array = utils.cli_to_array(cli_query)
|
||||||
|
expected_query = [
|
||||||
|
{"field": "this", "type": "", "value": "34", "op": "le"},
|
||||||
|
{"field": "that", "type": "string", "value": "foo", "op": "eq"}]
|
||||||
|
self.assertEqual(expected_query, ret_array)
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import re
|
||||||
|
|
||||||
import pyparsing as pp
|
import pyparsing as pp
|
||||||
|
|
||||||
@ -48,6 +48,16 @@ expr = pp.operatorPrecedence(condition, [
|
|||||||
("∨", 2, pp.opAssoc.LEFT, ),
|
("∨", 2, pp.opAssoc.LEFT, ),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
OP_LOOKUP = {'!=': 'ne',
|
||||||
|
'>=': 'ge',
|
||||||
|
'<=': 'le',
|
||||||
|
'>': 'gt',
|
||||||
|
'<': 'lt',
|
||||||
|
'=': 'eq'}
|
||||||
|
|
||||||
|
OP_LOOKUP_KEYS = '|'.join(sorted(OP_LOOKUP.keys(), key=len, reverse=True))
|
||||||
|
OP_SPLIT_RE = re.compile(r'(%s)' % OP_LOOKUP_KEYS)
|
||||||
|
|
||||||
|
|
||||||
def _parsed_query2dict(parsed_query):
|
def _parsed_query2dict(parsed_query):
|
||||||
result = None
|
result = None
|
||||||
@ -125,3 +135,44 @@ def dict_to_querystring(objs):
|
|||||||
return "&".join(["%s=%s" % (k, v)
|
return "&".join(["%s=%s" % (k, v)
|
||||||
for k, v in objs.items()
|
for k, v in objs.items()
|
||||||
if v is not None])
|
if v is not None])
|
||||||
|
|
||||||
|
|
||||||
|
def cli_to_array(cli_query):
|
||||||
|
"""Convert CLI list of queries to the Python API format.
|
||||||
|
|
||||||
|
This will convert the following:
|
||||||
|
"this<=34;that=string::foo"
|
||||||
|
to
|
||||||
|
"[{field=this,op=le,value=34,type=''},
|
||||||
|
{field=that,op=eq,value=foo,type=string}]"
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
opts = []
|
||||||
|
queries = cli_query.split(';')
|
||||||
|
for q in queries:
|
||||||
|
try:
|
||||||
|
field, q_operator, type_value = OP_SPLIT_RE.split(q, maxsplit=1)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError('Invalid or missing operator in query %(q)s,'
|
||||||
|
'the supported operators are: %(k)s' %
|
||||||
|
{'q': q, 'k': OP_LOOKUP.keys()})
|
||||||
|
if not field:
|
||||||
|
raise ValueError('Missing field in query %s' % q)
|
||||||
|
if not type_value:
|
||||||
|
raise ValueError('Missing value in query %s' % q)
|
||||||
|
opt = dict(field=field, op=OP_LOOKUP[q_operator])
|
||||||
|
|
||||||
|
if '::' not in type_value:
|
||||||
|
opt['type'], opt['value'] = '', type_value
|
||||||
|
else:
|
||||||
|
opt['type'], _, opt['value'] = type_value.partition('::')
|
||||||
|
|
||||||
|
if opt['type'] and opt['type'] not in (
|
||||||
|
'string', 'integer', 'float', 'datetime', 'boolean'):
|
||||||
|
err = ('Invalid value type %(type)s, the type of value'
|
||||||
|
'should be one of: integer, string, float, datetime,'
|
||||||
|
' boolean.' % opt['type'])
|
||||||
|
raise ValueError(err)
|
||||||
|
opts.append(opt)
|
||||||
|
return opts
|
||||||
|
@ -156,10 +156,14 @@ class CliAlarmCreate(show.ShowOne):
|
|||||||
|
|
||||||
common_group = parser.add_argument_group('common alarm rules')
|
common_group = parser.add_argument_group('common alarm rules')
|
||||||
common_group.add_argument(
|
common_group.add_argument(
|
||||||
'-q', '--query', metavar='<QUERY>', dest='query',
|
'--query', metavar='<QUERY>', dest='query',
|
||||||
help='key[op]data_type::value; list. data_type is optional, '
|
help="For alarms of type threshold or event: "
|
||||||
'but if supplied must be string, integer, float, or boolean. '
|
"key[op]data_type::value; list. data_type is optional, "
|
||||||
'Used by threshold and event alarms')
|
"but if supplied must be string, integer, float, or boolean. "
|
||||||
|
'For alarms of '
|
||||||
|
'type gnocchi_aggregation_by_resources_threshold: '
|
||||||
|
'need to specify a complex query json string, like:'
|
||||||
|
' {"and": [{"=": {"ended_at": null}}, ...]}.')
|
||||||
common_group.add_argument(
|
common_group.add_argument(
|
||||||
'--comparison-operator', metavar='<OPERATOR>',
|
'--comparison-operator', metavar='<OPERATOR>',
|
||||||
dest='comparison_operator', choices=ALARM_OPERATORS,
|
dest='comparison_operator', choices=ALARM_OPERATORS,
|
||||||
@ -285,6 +289,8 @@ class CliAlarmCreate(show.ShowOne):
|
|||||||
'state', 'severity', 'enabled', 'alarm_actions',
|
'state', 'severity', 'enabled', 'alarm_actions',
|
||||||
'ok_actions', 'insufficient_data_actions',
|
'ok_actions', 'insufficient_data_actions',
|
||||||
'time_constraints', 'repeat_actions'])
|
'time_constraints', 'repeat_actions'])
|
||||||
|
if parsed_args.type in ('threshold', 'event') and parsed_args.query:
|
||||||
|
parsed_args.query = utils.cli_to_array(parsed_args.query)
|
||||||
alarm['threshold_rule'] = utils.dict_from_parsed_args(
|
alarm['threshold_rule'] = utils.dict_from_parsed_args(
|
||||||
parsed_args, ['meter_name', 'period', 'evaluation_periods',
|
parsed_args, ['meter_name', 'period', 'evaluation_periods',
|
||||||
'statistic', 'comparison_operator', 'threshold',
|
'statistic', 'comparison_operator', 'threshold',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user