liusheng e27bfdf89d Fix the -q/--query in threshold alarm creation
Currently, when creating an threshold with aodhclient, we can use
-q/--query parameter to specify query conditions for alarm rule. but the
parameter is unavailable now.

* The -q parameter is unsupported by cliff, because -q/--quiet is internally
used by cliff. so we drop it.

* The --query cannot be used according to the help string because wrong
implementation.

* The --query usages of threshold/event type alarms and
gnocchi_aggregation_by_resources_threshold type alarms are different.

Closes-Bug: #1550094
Change-Id: I0a1235d12cd6aac80e81aa1c583bc79e2e50667f
2016-03-14 09:16:22 +08:00

340 lines
16 KiB
Python

#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cliff import command
from cliff import lister
from cliff import show
from oslo_serialization import jsonutils
from oslo_utils import strutils
from aodhclient import utils
ALARM_TYPES = ['threshold', 'event', 'composite',
'gnocchi_resources_threshold',
'gnocchi_aggregation_by_metrics_threshold',
'gnocchi_aggregation_by_resources_threshold']
ALARM_STATES = ['ok', 'alarm', 'insufficient data']
ALARM_SEVERITY = ['low', 'moderate', 'critical']
ALARM_OPERATORS = ['lt', 'le', 'eq', 'ne', 'ge', 'gt']
STATISTICS = ['max', 'min', 'avg', 'sum', 'count']
ALARM_LIST_COLS = ['alarm_id', 'type', 'name', 'state', 'severity', 'enabled']
class CliAlarmList(lister.Lister):
"""List alarms"""
def get_parser(self, prog_name):
parser = super(CliAlarmList, self).get_parser(prog_name)
parser.add_argument('-t', '--type', required=True,
choices=ALARM_TYPES, help='Type of alarm')
return parser
def take_action(self, parsed_args):
alarms = self.app.client.alarm.list(alarm_type=parsed_args.type)
return utils.list2cols(ALARM_LIST_COLS, alarms)
class CliAlarmSearch(CliAlarmList):
"""Search alarms with specified query rules"""
def get_parser(self, prog_name):
parser = super(CliAlarmSearch, self).get_parser(prog_name)
parser.add_argument("--query", help="Query"),
return parser
def take_action(self, parsed_args):
type_query = '{"=": {"type": "%s"}}' % parsed_args.type
if parsed_args.query:
query = '{"and": [%s, %s]}' % (type_query, parsed_args.query)
else:
query = type_query
alarms = self.app.client.alarm.search(query=query)
return utils.list2cols(ALARM_LIST_COLS, alarms)
def _format_alarm(alarm):
if alarm.get('composite_rule'):
composite_rule = jsonutils.dumps(alarm['composite_rule'], indent=2)
alarm['composite_rule'] = composite_rule
return alarm
for alarm_type in ALARM_TYPES:
if alarm.get('%s_rule' % alarm_type):
alarm.update(alarm.pop('%s_rule' % alarm_type))
return alarm
class CliAlarmShow(show.ShowOne):
"""Show an alarm"""
def get_parser(self, prog_name):
parser = super(CliAlarmShow, self).get_parser(prog_name)
parser.add_argument("alarm_id", help="ID of an alarm")
return parser
def take_action(self, parsed_args):
alarm = self.app.client.alarm.get(alarm_id=parsed_args.alarm_id)
return self.dict2columns(_format_alarm(alarm))
class CliAlarmCreate(show.ShowOne):
"""Create an alarm"""
create = True
def get_parser(self, prog_name):
parser = super(CliAlarmCreate, self).get_parser(prog_name)
parser.add_argument('-t', '--type', metavar='<TYPE>',
required=self.create,
choices=ALARM_TYPES, help='Type of alarm')
parser.add_argument('--name', metavar='<NAME>', required=self.create,
help='Name of the alarm')
parser.add_argument('--project-id', metavar='<PROJECT_ID>',
help='Project to associate with alarm '
'(configurable by admin users only)')
parser.add_argument('--user-id', metavar='<USER_ID>',
help='User to associate with alarm '
'(configurable by admin users only)')
parser.add_argument('--description', metavar='<DESCRIPTION>',
help='Free text description of the alarm')
parser.add_argument('--state', metavar='<STATE>',
choices=ALARM_STATES,
help='State of the alarm, one of: '
+ str(ALARM_STATES))
parser.add_argument('--severity', metavar='<SEVERITY>',
choices=ALARM_SEVERITY,
help='Severity of the alarm, one of: '
+ str(ALARM_SEVERITY))
parser.add_argument('--enabled', type=strutils.bool_from_string,
metavar='{True|False}',
help=('True if alarm evaluation is enabled'))
parser.add_argument('--alarm-action', dest='alarm_actions',
metavar='<Webhook URL>', action='append',
help=('URL to invoke when state transitions to '
'alarm. May be used multiple times'))
parser.add_argument('--ok-action', dest='ok_actions',
metavar='<Webhook URL>', action='append',
help=('URL to invoke when state transitions to'
'OK. May be used multiple times'))
parser.add_argument('--insufficient-data-action',
dest='insufficient_data_actions',
metavar='<Webhook URL>', action='append',
help=('URL to invoke when state transitions to '
'insufficient data. May be used multiple '
'times'))
parser.add_argument(
'--time-constraint', dest='time_constraints',
metavar='<Time Constraint>', action='append',
help=('Only evaluate the alarm if the time at evaluation '
'is within this time constraint. Start point(s) of '
'the constraint are specified with a cron expression'
', whereas its duration is given in seconds. '
'Can be specified multiple times for multiple '
'time constraints, format is: '
'name=<CONSTRAINT_NAME>;start=<CRON>;'
'duration=<SECONDS>;[description=<DESCRIPTION>;'
'[timezone=<IANA Timezone>]]'))
parser.add_argument('--repeat-actions', dest='repeat_actions',
metavar='{True|False}',
type=strutils.bool_from_string,
help=('True if actions should be repeatedly '
'notified while alarm remains in target '
'state'))
common_group = parser.add_argument_group('common alarm rules')
common_group.add_argument(
'--query', metavar='<QUERY>', dest='query',
help="For alarms of type threshold or event: "
"key[op]data_type::value; list. data_type is optional, "
"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(
'--comparison-operator', metavar='<OPERATOR>',
dest='comparison_operator', choices=ALARM_OPERATORS,
help='Operator to compare with, one of: ' + str(ALARM_OPERATORS))
common_group.add_argument(
'--evaluation-periods', type=int, metavar='<EVAL_PERIODS>',
dest='evaluation_periods',
help='Number of periods to evaluate over')
common_group.add_argument(
'--threshold', type=float, metavar='<THRESHOLD>',
dest='threshold', help='Threshold to evaluate against.')
common_group.add_argument(
'--metric', metavar='<METRIC>',
dest='metric', help='Metric to evaluate against.')
threshold_group = parser.add_argument_group('threshold alarm')
threshold_group.add_argument(
'-m', '--meter-name', metavar='<METRIC>',
dest='meter_name', help='Metric to evaluate against')
threshold_group.add_argument(
'--period', type=int, metavar='<PERIOD>', dest='period',
help='Length of each period (seconds) to evaluate over.')
threshold_group.add_argument(
'--statistic', metavar='<STATISTIC>', dest='statistic',
choices=STATISTICS,
help='Statistic to evaluate, one of: ' + str(STATISTICS))
event_group = parser.add_argument_group('event alarm')
event_group.add_argument(
'--event-type', metavar='<EVENT_TYPE>',
dest='event_type', help='Event type to evaluate against')
gnocchi_common_group = parser.add_argument_group(
'common gnocchi alarm rules')
gnocchi_common_group.add_argument(
'--granularity', metavar='<GRANULARITY>',
dest='granularity',
help='The time range in seconds over which to query.')
gnocchi_common_group.add_argument(
'--aggregation-method', metavar='<AGGR_METHOD>',
dest='aggregation_method',
help='The aggregation_method to compare to the threshold.')
gnocchi_resource_threshold_group = parser.add_argument_group(
'gnocchi resource threshold alarm')
gnocchi_resource_threshold_group.add_argument(
'--resource-type', metavar='<RESOURCE_TYPE>',
dest='resource_type', help='The type of resource.')
gnocchi_resource_threshold_group.add_argument(
'--resource-id', metavar='<RESOURCE_ID>',
dest='resource_id', help='The id of a resource.')
gnocchi_aggr_metrics_group = parser.add_argument_group(
'gnocchi aggregation by metrics alarm')
gnocchi_aggr_metrics_group.add_argument(
'--metrics', metavar='<METRICS>', action='append',
dest='metrics', help='The list of metric ids.')
composite_group = parser.add_argument_group('composite alarm')
composite_group.add_argument(
'--composite-rule', metavar='<COMPOSITE_RULE>',
dest='composite_rule',
type=jsonutils.loads,
help='Composite threshold rule with JSON format, the form can'
'be a nested dict which combine threshold/gnocchi rules by'
' "and", "or". For example, the form is like: '
'{"or":[RULE1, RULE2, {"and": [RULE3, RULE4]}]}, The'
'RULEx can be basic threshold rules but must include a'
'"type" field, like this: {"threshold": 0.8,'
'"meter_name":"cpu_util","type":"threshold"}'
)
self.parser = parser
return parser
def _validate_args(self, parsed_args):
if (parsed_args.type == 'threshold' and
not (parsed_args.meter_name and parsed_args.threshold)):
self.parser.error('threshold alarm requires -m/--meter-name and '
'--threshold parameters')
elif (parsed_args.type == 'gnocchi_resources_threshold' and
not (parsed_args.metric and parsed_args.threshold and
parsed_args.resource_id and parsed_args.resource_type
and parsed_args.aggregation_method)):
self.parser.error('gnocchi_resources_threshold requires --metric, '
'--threshold, --resource-id, --resource-type '
'and --aggregation-method')
elif (parsed_args.type == 'gnocchi_aggregation_by_metrics_threshold'
and not (parsed_args.metrics and parsed_args.threshold and
parsed_args.aggregation_method)):
self.parser.error('gnocchi_aggregation_by_metrics_threshold '
'requires --metrics, --threshold and '
'--aggregation-method')
elif (parsed_args.type == 'gnocchi_aggregation_by_resources_threshold'
and not (parsed_args.metric and parsed_args.threshold and
parsed_args.query and parsed_args.resource_type and
parsed_args.aggregation_method)):
self.parser.error('gnocchi_aggregation_by_resources_threshold '
'requires --metric, --threshold, '
'--aggregation-method, --query and '
'--resource-type')
elif (parsed_args.type == 'composite' and
not parsed_args.composite_rule):
self.parser.error('composite alarm requires'
' --composite-rule parameter')
def _alarm_from_args(self, parsed_args):
alarm = utils.dict_from_parsed_args(
parsed_args, ['name', 'project_id', 'user_id', 'description',
'state', 'severity', 'enabled', 'alarm_actions',
'ok_actions', 'insufficient_data_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(
parsed_args, ['meter_name', 'period', 'evaluation_periods',
'statistic', 'comparison_operator', 'threshold',
'query'])
alarm['event_rule'] = utils.dict_from_parsed_args(
parsed_args, ['event_type', 'query'])
alarm['gnocchi_resources_threshold_rule'] = (
utils.dict_from_parsed_args(parsed_args,
['granularity', 'comparison_operator',
'threshold', 'aggregation_method',
'evaluation_periods', 'metric',
'resource_id', 'resource_type']))
alarm['gnocchi_aggregation_by_metrics_threshold_rule'] = (
utils.dict_from_parsed_args(parsed_args,
['granularity', 'comparison_operator',
'threshold', 'aggregation_method',
'evaluation_periods', 'metrics']))
alarm['gnocchi_aggregation_by_resources_threshold_rule'] = (
utils.dict_from_parsed_args(parsed_args,
['granularity', 'comparison_operator',
'threshold', 'aggregation_method',
'evaluation_periods', 'metric',
'query', 'resource_type']))
alarm['composite_rule'] = parsed_args.composite_rule
if self.create:
alarm['type'] = parsed_args.type
self._validate_args(parsed_args)
return alarm
def take_action(self, parsed_args):
alarm = self.app.client.alarm.create(
alarm=self._alarm_from_args(parsed_args))
return self.dict2columns(_format_alarm(alarm))
class CliAlarmUpdate(CliAlarmCreate):
"""Update an alarm"""
create = False
def get_parser(self, prog_name):
parser = super(CliAlarmUpdate, self).get_parser(prog_name)
parser.add_argument("alarm_id", help="ID of the alarm")
return parser
def take_action(self, parsed_args):
attributes = self._alarm_from_args(parsed_args)
updated_alarm = self.app.client.alarm.update(
alarm_id=parsed_args.alarm_id, alarm_update=attributes)
return self.dict2columns(_format_alarm(updated_alarm))
class CliAlarmDelete(command.Command):
"""Delete an alarm"""
def get_parser(self, prog_name):
parser = super(CliAlarmDelete, self).get_parser(prog_name)
parser.add_argument("alarm_id", help="ID of the alarm")
return parser
def take_action(self, parsed_args):
self.app.client.alarm.delete(parsed_args.alarm_id)