# -*- encoding: utf-8 -*- # # Copyright © 2013 Red Hat, Inc # # Author: Angus Salkeld # # 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. import functools import json import six from ceilometerclient.common import utils from ceilometerclient import exc from ceilometerclient.v2 import options ALARM_STATES = ['ok', 'alarm', 'insufficient_data'] ALARM_OPERATORS = ['lt', 'le', 'eq', 'ne', 'ge', 'gt'] ALARM_COMBINATION_OPERATORS = ['and', 'or'] STATISTICS = ['max', 'min', 'avg', 'sum', 'count'] OPERATORS_STRING = dict(gt='>', ge='>=', lt='<', le="<=", eq='==', ne='!=') @utils.arg('-q', '--query', metavar='', help='key[op]value; list.') @utils.arg('-m', '--meter', metavar='', required=True, help='Name of meter to show samples for.') @utils.arg('-p', '--period', metavar='', help='Period in seconds over which to group samples.') def do_statistics(cc, args): '''List the statistics for this meter.''' fields = {'meter_name': args.meter, 'q': options.cli_to_array(args.query), 'period': args.period} try: statistics = cc.statistics.list(**fields) except exc.HTTPNotFound: raise exc.CommandError('Samples not found: %s' % args.meter) else: field_labels = ['Period', 'Period Start', 'Period End', 'Count', 'Min', 'Max', 'Sum', 'Avg', 'Duration', 'Duration Start', 'Duration End'] fields = ['period', 'period_start', 'period_end', 'count', 'min', 'max', 'sum', 'avg', 'duration', 'duration_start', 'duration_end'] utils.print_list(statistics, fields, field_labels) @utils.arg('-q', '--query', metavar='', help='key[op]value; list.') @utils.arg('-m', '--meter', metavar='', required=True, help='Name of meter to show samples for.') @utils.arg('-l', '--limit', metavar='', help='Maximum number of samples to return.') def do_sample_list(cc, args): '''List the samples for this meters.''' fields = {'meter_name': args.meter, 'q': options.cli_to_array(args.query), 'limit': args.limit} try: samples = cc.samples.list(**fields) except exc.HTTPNotFound: raise exc.CommandError('Samples not found: %s' % args.meter) else: field_labels = ['Resource ID', 'Name', 'Type', 'Volume', 'Unit', 'Timestamp'] fields = ['resource_id', 'counter_name', 'counter_type', 'counter_volume', 'counter_unit', 'timestamp'] utils.print_list(samples, fields, field_labels, sortby=None) @utils.arg('--project-id', metavar='', help='Tenant to associate with sample ' '(only settable by admin users)') @utils.arg('--user-id', metavar='', help='User to associate with sample ' '(only settable by admin users)') @utils.arg('-r', '--resource-id', metavar='', required=True, help='ID of the resource.') @utils.arg('-m', '--meter-name', metavar='', help='the meter name') @utils.arg('--meter-type', metavar='', required=True, help='the meter type') @utils.arg('--meter-unit', metavar='', required=True, help='the meter unit') @utils.arg('--sample-volume', metavar='', required=True, help='The sample volume') @utils.arg('--resource-metadata', metavar='', help='resource metadata') def do_sample_create(cc, args={}): '''Create a sample.''' arg_to_field_mapping = {'meter_name': 'counter_name', 'meter_unit': 'counter_unit', 'meter_type': 'counter_type', 'sample_volume': 'counter_volume'} fields = {} for var in vars(args).items(): k, v = var[0], var[1] if v is not None: if k == 'resource_metadata': fields[k] = json.loads(v) else: fields[arg_to_field_mapping.get(k, k)] = v cc.samples.create(**fields) @utils.arg('-q', '--query', metavar='', help='key[op]value; list.') def do_meter_list(cc, args={}): '''List the user's meters.''' meters = cc.meters.list(q=options.cli_to_array(args.query)) field_labels = ['Name', 'Type', 'Unit', 'Resource ID', 'User ID', 'Project ID'] fields = ['name', 'type', 'unit', 'resource_id', 'user_id', 'project_id'] utils.print_list(meters, fields, field_labels, sortby=0) def _display_rule(type, rule): if type == 'threshold': return ('%(meter_name)s %(comparison_operator)s ' '%(threshold)s during %(evaluation_periods)s x %(period)ss' % { 'meter_name': rule['meter_name'], 'threshold': rule['threshold'], 'evaluation_periods': rule['evaluation_periods'], 'period': rule['period'], 'comparison_operator': OPERATORS_STRING.get( rule['comparison_operator']) }) elif type == 'combination': return ('combinated states (%(operator)s) of %(alarms)s' % { 'operator': rule['operator'].upper(), 'alarms': ", ".join(rule['alarm_ids'])}) else: # just dump all return "\n".join(["%s: %s" % (f, v) for f, v in rule.iteritems()]) def alarm_rule_formatter(alarm): return _display_rule(alarm.type, alarm.rule) def _infer_type(detail): if 'type' in detail: return detail['type'] elif 'meter_name' in detail['rule']: return 'threshold' elif 'alarms' in detail['rule']: return 'combination' else: return 'unknown' def alarm_change_detail_formatter(change): detail = json.loads(change.detail) fields = [] if change.type == 'state transition': fields.append('state: %s' % detail['state']) elif change.type == 'creation' or change.type == 'deletion': for k in ['name', 'description', 'type', 'rule']: if k == 'rule': fields.append('rule: %s' % _display_rule(detail['type'], detail[k])) else: fields.append('%s: %s' % (k, detail[k])) elif change.type == 'rule change': for k, v in six.iteritems(detail): if k == 'rule': fields.append('rule: %s' % _display_rule(_infer_type(detail), v)) else: fields.append('%s: %s' % (k, v)) return '\n'.join(fields) @utils.arg('-q', '--query', metavar='', help='key[op]value; list.') def do_alarm_list(cc, args={}): '''List the user's alarms.''' alarms = cc.alarms.list(q=options.cli_to_array(args.query)) # omit action initially to keep output width sane # (can switch over to vertical formatting when available from CLIFF) field_labels = ['Alarm ID', 'Name', 'State', 'Enabled', 'Continuous', 'Alarm condition'] fields = ['alarm_id', 'name', 'state', 'enabled', 'repeat_actions', 'rule'] utils.print_list(alarms, fields, field_labels, formatters={'rule': alarm_rule_formatter}, sortby=0) def alarm_query_formater(alarm): qs = [] for q in alarm.rule['query']: qs.append('%s %s %s' % ( q['field'], OPERATORS_STRING.get(q['op']), q['value'])) return r' AND\n'.join(qs) def _display_alarm(alarm): fields = ['name', 'description', 'type', 'state', 'enabled', 'alarm_id', 'user_id', 'project_id', 'alarm_actions', 'ok_actions', 'insufficient_data_actions', 'repeat_actions'] data = dict([(f, getattr(alarm, f, '')) for f in fields]) data.update(alarm.rule) if alarm.type == 'threshold': data['query'] = alarm_query_formater(alarm) utils.print_dict(data, wrap=72) @utils.arg('-a', '--alarm_id', metavar='', required=True, help='ID of the alarm to show.') def do_alarm_show(cc, args={}): '''Show an alarm.''' try: alarm = cc.alarms.get(args.alarm_id) except exc.HTTPNotFound: raise exc.CommandError('Alarm not found: %s' % args.alarm_id) else: _display_alarm(alarm) def common_alarm_arguments(create=False): def _wrapper(func): @utils.arg('--name', metavar='', required=create, help='Name of the alarm (must be unique per tenant)') @utils.arg('--project-id', metavar='', help='Tenant to associate with alarm ' '(only settable by admin users)') @utils.arg('--user-id', metavar='', help='User to associate with alarm ' '(only settable by admin users)') @utils.arg('--description', metavar='', help='Free text description of the alarm') @utils.arg('--state', metavar='', help='State of the alarm, one of: ' + str(ALARM_STATES)) @utils.arg('--enabled', type=utils.string_to_bool, metavar='{True|False}', help='True if alarm evaluation/actioning is enabled') @utils.arg('--alarm-action', dest='alarm_actions', metavar='', action='append', default=None, help=('URL to invoke when state transitions to alarm. ' 'May be used multiple times.')) @utils.arg('--ok-action', dest='ok_actions', metavar='', action='append', default=None, help=('URL to invoke when state transitions to OK. ' 'May be used multiple times.')) @utils.arg('--insufficient-data-action', dest='insufficient_data_actions', metavar='', action='append', default=None, help=('URL to invoke when state transitions to unkown. ' 'May be used multiple times.')) @functools.wraps(func) def _wrapped(*args, **kwargs): return func(*args, **kwargs) return _wrapped return _wrapper @common_alarm_arguments(create=True) @utils.arg('--period', type=int, metavar='', help='Length of each period (seconds) to evaluate over') @utils.arg('--evaluation-periods', type=int, metavar='', help='Number of periods to evaluate over') @utils.arg('--meter-name', metavar='', required=True, help='Metric to evaluate against') @utils.arg('--statistic', metavar='', help='Statistic to evaluate, one of: ' + str(STATISTICS)) @utils.arg('--comparison-operator', metavar='', help='Operator to compare with, one of: ' + str(ALARM_OPERATORS)) @utils.arg('--threshold', type=float, metavar='', required=True, help='Threshold to evaluate against') @utils.arg('--matching-metadata', dest='matching_metadata', metavar='', action='append', default=None, help=('A meter should match this resource metadata (key=value) ' 'additionally to the meter_name')) @utils.arg('--repeat-actions', dest='repeat_actions', metavar='{True|False}', type=utils.string_to_bool, default=False, help=('True if actions should be repeatedly notified ' 'while alarm remains in target state')) def do_alarm_create(cc, args={}): '''Create a new alarm (Deprecated).''' fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) fields = utils.args_array_to_dict(fields, "matching_metadata") alarm = cc.alarms.create(**fields) _display_alarm(alarm) @common_alarm_arguments(create=True) @utils.arg('--meter-name', metavar='', required=True, dest='threshold_rule/meter_name', help='Metric to evaluate against') @utils.arg('--period', type=int, metavar='', dest='threshold_rule/period', help='Length of each period (seconds) to evaluate over') @utils.arg('--evaluation-periods', type=int, metavar='', dest='threshold_rule/evaluation_periods', help='Number of periods to evaluate over') @utils.arg('--statistic', metavar='', dest='threshold_rule/statistic', help='Statistic to evaluate, one of: ' + str(STATISTICS)) @utils.arg('--comparison-operator', metavar='', dest='threshold_rule/comparison_operator', help='Operator to compare with, one of: ' + str(ALARM_OPERATORS)) @utils.arg('--threshold', type=float, metavar='', required=True, dest='threshold_rule/threshold', help='Threshold to evaluate against') @utils.arg('-q', '--query', metavar='', dest='threshold_rule/query', help='The query to find the data for computing statistics ' '(key[op]value; list.)') @utils.arg('--repeat-actions', dest='repeat_actions', metavar='{True|False}', type=utils.string_to_bool, default=False, help=('True if actions should be repeatedly notified ' 'while alarm remains in target state')) def do_alarm_threshold_create(cc, args={}): '''Create a new alarm based on computed statistics.''' fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) fields = utils.key_with_slash_to_nested_dict(fields) fields['type'] = 'threshold' if 'query' in fields['threshold_rule']: fields['threshold_rule']['query'] = options.cli_to_array( fields['threshold_rule']['query']) alarm = cc.alarms.create(**fields) _display_alarm(alarm) @common_alarm_arguments(create=True) @utils.arg('--alarm_ids', action='append', metavar='', required=True, dest='combination_rule/alarm_ids', help='List of alarm id') @utils.arg('--operator', metavar='', dest='combination_rule/operator', help='Operator to compare with, one of: ' + str( ALARM_COMBINATION_OPERATORS)) @utils.arg('--repeat-actions', dest='repeat_actions', metavar='{True|False}', type=utils.string_to_bool, default=False, help=('True if actions should be repeatedly notified ' 'while alarm remains in target state')) def do_alarm_combination_create(cc, args={}): '''Create a new alarm based on state of other alarms.''' fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) fields = utils.key_with_slash_to_nested_dict(fields) fields['type'] = 'combination' alarm = cc.alarms.create(**fields) _display_alarm(alarm) @utils.arg('-a', '--alarm_id', metavar='', required=True, help='ID of the alarm to update.') @common_alarm_arguments() @utils.arg('--period', type=int, metavar='', help='Length of each period (seconds) to evaluate over') @utils.arg('--evaluation-periods', type=int, metavar='', help='Number of periods to evaluate over') @utils.arg('--meter-name', metavar='', help='Metric to evaluate against') @utils.arg('--statistic', metavar='', help='Statistic to evaluate, one of: ' + str(STATISTICS)) @utils.arg('--comparison-operator', metavar='', help='Operator to compare with, one of: ' + str(ALARM_OPERATORS)) @utils.arg('--threshold', type=float, metavar='', help='Threshold to evaluate against') @utils.arg('--matching-metadata', dest='matching_metadata', metavar='', action='append', default=None, help=('A meter should match this resource metadata (key=value) ' 'additionally to the meter_name')) @utils.arg('--repeat-actions', dest='repeat_actions', metavar='{True|False}', type=utils.string_to_bool, help=('True if actions should be repeatedly notified ' 'while alarm remains in target state')) def do_alarm_update(cc, args={}): '''Update an existing alarm.''' fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) fields = utils.args_array_to_dict(fields, "matching_metadata") fields.pop('alarm_id') try: alarm = cc.alarms.update(args.alarm_id, **fields) except exc.HTTPNotFound: raise exc.CommandError('Alarm not found: %s' % args.alarm_id) _display_alarm(alarm) @utils.arg('-a', '--alarm_id', metavar='', required=True, help='ID of the alarm to update.') @common_alarm_arguments() @utils.arg('--meter-name', metavar='', dest='threshold_rule/meter_name', help='Metric to evaluate against') @utils.arg('--period', type=int, metavar='', dest='threshold_rule/period', help='Length of each period (seconds) to evaluate over') @utils.arg('--evaluation-periods', type=int, metavar='', dest='threshold_rule/evaluation_periods', help='Number of periods to evaluate over') @utils.arg('--statistic', metavar='', dest='threshold_rule/statistic', help='Statistic to evaluate, one of: ' + str(STATISTICS)) @utils.arg('--comparison-operator', metavar='', dest='threshold_rule/comparison_operator', help='Operator to compare with, one of: ' + str(ALARM_OPERATORS)) @utils.arg('--threshold', type=float, metavar='', dest='threshold_rule/threshold', help='Threshold to evaluate against') @utils.arg('-q', '--query', metavar='', dest='threshold_rule/query', help='The query to find the data for computing statistics ' '(key[op]value; list.)') @utils.arg('--repeat-actions', dest='repeat_actions', metavar='{True|False}', type=utils.string_to_bool, help=('True if actions should be repeatedly notified ' 'while alarm remains in target state')) def do_alarm_threshold_update(cc, args={}): '''Update an existing alarm based on computed statistics.''' fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) fields = utils.key_with_slash_to_nested_dict(fields) fields.pop('alarm_id') fields['type'] = 'threshold' if 'query' in fields['threshold_rule']: fields['threshold_rule']['query'] = options.cli_to_array( fields['threshold_rule']['query']) try: alarm = cc.alarms.update(args.alarm_id, **fields) except exc.HTTPNotFound: raise exc.CommandError('Alarm not found: %s' % args.alarm_id) _display_alarm(alarm) @utils.arg('-a', '--alarm_id', metavar='', required=True, help='ID of the alarm to update.') @common_alarm_arguments() @utils.arg('--alarm_ids', action='append', metavar='', dest='combination_rule/alarm_ids', help='List of alarm id') @utils.arg('---operator', metavar='', dest='combination_rule/operator', help='Operator to compare with, one of: ' + str( ALARM_COMBINATION_OPERATORS)) @utils.arg('--repeat-actions', dest='repeat_actions', metavar='{True|False}', type=utils.string_to_bool, help=('True if actions should be repeatedly notified ' 'while alarm remains in target state')) def do_alarm_combination_update(cc, args={}): '''Update an existing alarm based on state of other alarms.''' fields = dict(filter(lambda x: not (x[1] is None), vars(args).items())) fields = utils.key_with_slash_to_nested_dict(fields) fields.pop('alarm_id') fields['type'] = 'combination' try: alarm = cc.alarms.update(args.alarm_id, **fields) except exc.HTTPNotFound: raise exc.CommandError('Alarm not found: %s' % args.alarm_id) _display_alarm(alarm) @utils.arg('-a', '--alarm_id', metavar='', required=True, help='ID of the alarm to delete.') def do_alarm_delete(cc, args={}): '''Delete an alarm.''' try: cc.alarms.delete(args.alarm_id) except exc.HTTPNotFound: raise exc.CommandError('Alarm not found: %s' % args.alarm_id) @utils.arg('-a', '--alarm_id', metavar='', required=True, help='ID of the alarm state to set.') @utils.arg('--state', metavar='', required=True, help='State of the alarm, one of: ' + str(ALARM_STATES)) def do_alarm_state_set(cc, args={}): '''Set the state of an alarm.''' try: state = cc.alarms.set_state(args.alarm_id, args.state) except exc.HTTPNotFound: raise exc.CommandError('Alarm not found: %s' % args.alarm_id) utils.print_dict({'state': state}, wrap=72) @utils.arg('-a', '--alarm_id', metavar='', required=True, help='ID of the alarm state to show.') def do_alarm_state_get(cc, args={}): '''Get the state of an alarm.''' try: state = cc.alarms.get_state(args.alarm_id) except exc.HTTPNotFound: raise exc.CommandError('Alarm not found: %s' % args.alarm_id) utils.print_dict({'state': state}, wrap=72) @utils.arg('-a', '--alarm_id', metavar='', required=True, help='ID of the alarm for which history is shown.') @utils.arg('-q', '--query', metavar='', help='key[op]value; list.') def do_alarm_history(cc, args={}): '''Display the change history of an alarm.''' kwargs = dict(alarm_id=args.alarm_id, q=options.cli_to_array(args.query)) try: history = cc.alarms.get_history(**kwargs) except exc.HTTPNotFound: raise exc.CommandError('Alarm not found: %s' % args.alarm_id) field_labels = ['Type', 'Timestamp', 'Detail'] fields = ['type', 'timestamp', 'detail'] utils.print_list(history, fields, field_labels, formatters={'detail': alarm_change_detail_formatter}, sortby=1) @utils.arg('-q', '--query', metavar='', help='key[op]value; list.') def do_resource_list(cc, args={}): '''List the resources.''' resources = cc.resources.list(q=options.cli_to_array(args.query)) field_labels = ['Resource ID', 'Source', 'User ID', 'Project ID'] fields = ['resource_id', 'source', 'user_id', 'project_id'] utils.print_list(resources, fields, field_labels, sortby=1) @utils.arg('-r', '--resource_id', metavar='', required=True, help='ID of the resource to show.') def do_resource_show(cc, args={}): '''Show the resource.''' try: resource = cc.resources.get(args.resource_id) except exc.HTTPNotFound: raise exc.CommandError('Resource not found: %s' % args.resource_id) else: fields = ['resource_id', 'source', 'user_id', 'project_id', 'metadata'] data = dict([(f, getattr(resource, f, '')) for f in fields]) utils.print_dict(data, wrap=72)