# (C) Copyright 2014-2017 Hewlett Packard Enterprise Development LP # Copyright 2017 FUJITSU LIMITED # # 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 datetime import numbers import time from keystoneauth1 import exceptions as k_exc from osc_lib import exceptions as osc_exc from monascaclient.common import utils from oslo_serialization import jsonutils # Alarm valid types severity_types = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'] state_types = ['UNDETERMINED', 'ALARM', 'OK'] enabled_types = ['True', 'true', 'False', 'false'] group_by_types = ['alarm_definition_id', 'name', 'state', 'severity', 'link', 'lifecycle_state', 'metric_name', 'dimension_name', 'dimension_value'] allowed_notification_sort_by = {'id', 'name', 'type', 'address', 'created_at', 'updated_at'} allowed_alarm_sort_by = {'alarm_id', 'alarm_definition_id', 'alarm_definition_name', 'state', 'severity', 'lifecycle_state', 'link', 'state_updated_timestamp', 'updated_timestamp', 'created_timestamp'} allowed_definition_sort_by = {'id', 'name', 'severity', 'updated_at', 'created_at'} @utils.arg('name', metavar='', help='Name of the metric to create.') @utils.arg('--dimensions', metavar='', help='key value pair used to create a metric dimension. ' 'This can be specified multiple times, or once with parameters ' 'separated by a comma. ' 'Dimensions need quoting when they contain special chars [&,(,),{,},>,<] ' 'that confuse the CLI parser.', action='append') @utils.arg('--value-meta', metavar='', help='key value pair for extra information about a value. ' 'This can be specified multiple times, or once with parameters ' 'separated by a comma. ' 'value_meta need quoting when they contain special chars [&,(,),{,},>,<] ' 'that confuse the CLI parser.', action='append') @utils.arg('--time', metavar='', default=time.time() * 1000, type=int, help='Metric timestamp in milliseconds. Default: current timestamp.') @utils.arg('--project-id', metavar='', help='The Project ID to create metric on behalf of. ' 'Requires monitoring-delegate role in keystone.') @utils.arg('value', metavar='', type=float, help='Metric value.') def do_metric_create(mc, args): '''Create metric.''' fields = {} fields['name'] = args.name if args.dimensions: fields['dimensions'] = utils.format_parameters(args.dimensions) fields['timestamp'] = args.time fields['value'] = args.value if args.value_meta: fields['value_meta'] = utils.format_parameters(args.value_meta) if args.project_id: fields['tenant_id'] = args.project_id try: mc.metrics.create(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print('Successfully created metric') @utils.arg('jsonbody', metavar='', type=jsonutils.loads, help='The raw JSON body in single quotes. See api doc.') def do_metric_create_raw(mc, args): '''Create metric from raw json body.''' try: mc.metrics.create(**args.jsonbody) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print('Successfully created metric') @utils.arg('--dimensions', metavar='', help='key value pair used to specify a metric dimension. ' 'This can be specified multiple times, or once with parameters ' 'separated by a comma. ' 'Dimensions need quoting when they contain special chars [&,(,),{,},>,<] ' 'that confuse the CLI parser.', action='append') @utils.arg('--offset', metavar='', help='The offset used to paginate the return data.') @utils.arg('--limit', metavar='', help='The amount of data to be returned up to the API maximum limit.') @utils.arg('--tenant-id', metavar='', help="Retrieve data for the specified tenant/project id instead of " "the tenant/project from the user's Keystone credentials.") def do_metric_name_list(mc, args): '''List names of metrics.''' fields = {} if args.dimensions: fields['dimensions'] = utils.format_dimensions_query(args.dimensions) if args.limit: fields['limit'] = args.limit if args.offset: fields['offset'] = args.offset if args.tenant_id: fields['tenant_id'] = args.tenant_id try: metric_names = mc.metrics.list_names(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: if args.json: print(utils.json_formatter(metric_names)) return if isinstance(metric_names, list): utils.print_list(metric_names, ['Name'], formatters={'Name': lambda x: x['name']}) @utils.arg('--name', metavar='', help='Name of the metric to list.') @utils.arg('--dimensions', metavar='', help='key value pair used to specify a metric dimension. ' 'This can be specified multiple times, or once with parameters ' 'separated by a comma. ' 'Dimensions need quoting when they contain special chars [&,(,),{,},>,<] ' 'that confuse the CLI parser.', action='append') @utils.arg('--starttime', metavar='', help='measurements >= UTC time. format: 2014-01-01T00:00:00Z. OR' ' Format: -120 (previous 120 minutes).') @utils.arg('--endtime', metavar='', help='measurements <= UTC time. format: 2014-01-01T00:00:00Z.') @utils.arg('--offset', metavar='', help='The offset used to paginate the return data.') @utils.arg('--limit', metavar='', help='The amount of data to be returned up to the API maximum limit.') @utils.arg('--tenant-id', metavar='', help="Retrieve data for the specified tenant/project id instead of " "the tenant/project from the user's Keystone credentials.") def do_metric_list(mc, args): '''List metrics for this tenant.''' fields = {} if args.name: fields['name'] = args.name if args.dimensions: fields['dimensions'] = utils.format_dimensions_query(args.dimensions) if args.limit: fields['limit'] = args.limit if args.offset: fields['offset'] = args.offset if args.starttime: _translate_starttime(args) fields['start_time'] = args.starttime if args.endtime: fields['end_time'] = args.endtime if args.tenant_id: fields['tenant_id'] = args.tenant_id try: metric = mc.metrics.list(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: if args.json: print(utils.json_formatter(metric)) return cols = ['name', 'dimensions'] formatters = { 'name': lambda x: x['name'], 'dimensions': lambda x: utils.format_dict(x['dimensions']), } if isinstance(metric, list): # print the list utils.print_list(metric, cols, formatters=formatters) else: # add the dictionary to a list, so print_list works metric_list = list() metric_list.append(metric) utils.print_list( metric_list, cols, formatters=formatters) @utils.arg('--metric-name', metavar='', help='Name of the metric to report dimension name list.', action='append') @utils.arg('--offset', metavar='', help='The offset used to paginate the return data.') @utils.arg('--limit', metavar='', help='The amount of data to be returned up to the API maximum ' 'limit.') @utils.arg('--tenant-id', metavar='', help="Retrieve data for the specified tenant/project id instead of " "the tenant/project from the user's Keystone credentials.") def do_dimension_name_list(mc, args): '''List names of metric dimensions.''' fields = {} if args.metric_name: fields['metric_name'] = args.metric_name if args.limit: fields['limit'] = args.limit if args.offset: fields['offset'] = args.offset if args.tenant_id: fields['tenant_id'] = args.tenant_id try: dimension_names = mc.metrics.list_dimension_names(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) if args.json: print(utils.json_formatter(dimension_names)) return if isinstance(dimension_names, list): utils.print_list(dimension_names, ['Dimension Names'], formatters={ 'Dimension Names': lambda x: x['dimension_name']}) @utils.arg('dimension_name', metavar='', help='Name of the dimension to list dimension values.') @utils.arg('--metric-name', metavar='', help='Name of the metric to report dimension value list.', action='append') @utils.arg('--offset', metavar='', help='The offset used to paginate the return data.') @utils.arg('--limit', metavar='', help='The amount of data to be returned up to the API maximum ' 'limit.') @utils.arg('--tenant-id', metavar='', help="Retrieve data for the specified tenant/project id instead of " "the tenant/project from the user's Keystone credentials.") def do_dimension_value_list(mc, args): '''List names of metric dimensions.''' fields = {} fields['dimension_name'] = args.dimension_name if args.metric_name: fields['metric_name'] = args.metric_name if args.limit: fields['limit'] = args.limit if args.offset: fields['offset'] = args.offset if args.tenant_id: fields['tenant_id'] = args.tenant_id try: dimension_values = mc.metrics.list_dimension_values(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) if args.json: print(utils.json_formatter(dimension_values)) return if isinstance(dimension_values, list): utils.print_list(dimension_values, ['Dimension Values'], formatters={ 'Dimension Values': lambda x: x['dimension_value']}) def format_measure_timestamp(measurements): # returns newline separated times for the timestamp column return '\n'.join([str(m[0]) for m in measurements]) def format_measure_value(measurements): # returns newline separated values for the value column return '\n'.join(['{:12.3f}'.format(m[1]) for m in measurements]) def format_value_meta(measurements): # returns newline separated values for the value column measure_string_list = list() for measure in measurements: if len(measure) < 3: measure_string = "" else: meta_string_list = [] for k, v in measure[2].items(): if isinstance(v, numbers.Number): m_str = k + ': ' + str(v) else: m_str = k + ': ' + v meta_string_list.append(m_str) measure_string = ','.join(meta_string_list) measure_string_list.append(measure_string) return '\n'.join(measure_string_list) def format_statistic_timestamp(statistics, columns, name): # returns newline separated times for the timestamp column time_index = 0 if statistics: time_index = columns.index(name) time_list = list() for timestamp in statistics: time_list.append(str(timestamp[time_index])) return '\n'.join(time_list) def format_statistic_value(statistics, columns, stat_type): # find the index for column name stat_index = 0 if statistics: stat_index = columns.index(stat_type) value_list = list() for stat in statistics: value_str = '{:12.3f}'.format(stat[stat_index]) value_list.append(value_str) return '\n'.join(value_list) def format_metric_name(metrics): # returns newline separated metric names for the column metric_string_list = list() for metric in metrics: metric_name = metric['name'] metric_dimensions = metric['dimensions'] metric_string_list.append(metric_name) # need to line up with dimensions column rng = len(metric_dimensions) for i in range(rng): if i == rng - 1: # last one break metric_string_list.append(" ") return '\n'.join(metric_string_list) def format_metric_dimensions(metrics): # returns newline separated dimension key values for the column metric_string_list = list() for metric in metrics: metric_dimensions = metric['dimensions'] for k, v in metric_dimensions.items(): if isinstance(v, numbers.Number): d_str = k + ': ' + str(v) else: d_str = k + ': ' + v metric_string_list.append(d_str) return '\n'.join(metric_string_list) @utils.arg('name', metavar='', help='Name of the metric to list measurements.') @utils.arg('--dimensions', metavar='', help='key value pair used to specify a metric dimension. ' 'This can be specified multiple times, or once with parameters ' 'separated by a comma. ' 'Dimensions need quoting when they contain special chars [&,(,),{,},>,<] ' 'that confuse the CLI parser.', action='append') @utils.arg('starttime', metavar='', help='measurements >= UTC time. format: 2014-01-01T00:00:00Z.' ' OR Format: -120 (previous 120 minutes).') @utils.arg('--endtime', metavar='', help='measurements <= UTC time. format: 2014-01-01T00:00:00Z.') @utils.arg('--offset', metavar='', help='The offset used to paginate the return data.') @utils.arg('--limit', metavar='', help='The amount of data to be returned up to the API maximum limit.') @utils.arg('--merge_metrics', action='store_const', const=True, help='Merge multiple metrics into a single result.') @utils.arg('--group_by', metavar='', help='Select which keys to use for grouping. A \'*\' groups by all keys.') @utils.arg('--tenant-id', metavar='', help="Retrieve data for the specified tenant/project id instead of " "the tenant/project from the user's Keystone credentials.") def do_measurement_list(mc, args): '''List measurements for the specified metric.''' fields = {} fields['name'] = args.name if args.dimensions: fields['dimensions'] = utils.format_dimensions_query(args.dimensions) _translate_starttime(args) fields['start_time'] = args.starttime if args.endtime: fields['end_time'] = args.endtime if args.limit: fields['limit'] = args.limit if args.offset: fields['offset'] = args.offset if args.merge_metrics: fields['merge_metrics'] = args.merge_metrics if args.group_by: fields['group_by'] = args.group_by if args.tenant_id: fields['tenant_id'] = args.tenant_id try: metric = mc.metrics.list_measurements(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: if args.json: print(utils.json_formatter(metric)) return cols = ['name', 'dimensions', 'timestamp', 'value', 'value_meta'] formatters = { 'name': lambda x: x['name'], 'dimensions': lambda x: utils.format_dict(x['dimensions']), 'timestamp': lambda x: format_measure_timestamp(x['measurements']), 'value': lambda x: format_measure_value(x['measurements']), 'value_meta': lambda x: format_value_meta(x['measurements']), } if isinstance(metric, list): # print the list utils.print_list(metric, cols, formatters=formatters) else: # add the dictionary to a list, so print_list works metric_list = list() metric_list.append(metric) utils.print_list( metric_list, cols, formatters=formatters) @utils.arg('name', metavar='', help='Name of the metric to report measurement statistics.') @utils.arg('statistics', metavar='', help='Statistics is one or more (separated by commas) of ' '[AVG, MIN, MAX, COUNT, SUM].') @utils.arg('--dimensions', metavar='', help='key value pair used to specify a metric dimension. ' 'This can be specified multiple times, or once with parameters ' 'separated by a comma. ' 'Dimensions need quoting when they contain special chars [&,(,),{,},>,<] ' 'that confuse the CLI parser.', action='append') @utils.arg('starttime', metavar='', help='measurements >= UTC time. format: 2014-01-01T00:00:00Z. OR' ' Format: -120 (previous 120 minutes).') @utils.arg('--endtime', metavar='', help='measurements <= UTC time. format: 2014-01-01T00:00:00Z.') @utils.arg('--period', metavar='', help='number of seconds per interval (default is 300)') @utils.arg('--offset', metavar='', help='The offset used to paginate the return data.') @utils.arg('--limit', metavar='', help='The amount of data to be returned up to the API maximum limit.') @utils.arg('--merge_metrics', action='store_const', const=True, help='Merge multiple metrics into a single result.') @utils.arg('--group_by', metavar='', help='Select which keys to use for grouping. A \'*\' groups by all keys.') @utils.arg('--tenant-id', metavar='', help="Retrieve data for the specified tenant/project id instead of " "the tenant/project from the user's Keystone credentials.") def do_metric_statistics(mc, args): '''List measurement statistics for the specified metric.''' statistic_types = ['AVG', 'MIN', 'MAX', 'COUNT', 'SUM'] statlist = args.statistics.split(',') for stat in statlist: if stat.upper() not in statistic_types: errmsg = ('Invalid type, not one of [' + ', '.join(statistic_types) + ']') raise osc_exc.CommandError(errmsg) fields = {} fields['name'] = args.name if args.dimensions: fields['dimensions'] = utils.format_dimensions_query(args.dimensions) _translate_starttime(args) fields['start_time'] = args.starttime if args.endtime: fields['end_time'] = args.endtime if args.period: fields['period'] = args.period fields['statistics'] = args.statistics if args.limit: fields['limit'] = args.limit if args.offset: fields['offset'] = args.offset if args.merge_metrics: fields['merge_metrics'] = args.merge_metrics if args.group_by: fields['group_by'] = args.group_by if args.tenant_id: fields['tenant_id'] = args.tenant_id try: metric = mc.metrics.list_statistics(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: if args.json: print(utils.json_formatter(metric)) return cols = ['name', 'dimensions'] # add dynamic column names if metric: column_names = metric[0]['columns'] for name in column_names: cols.append(name) else: # when empty set, print_list needs a col cols.append('timestamp') formatters = { 'name': lambda x: x['name'], 'dimensions': lambda x: utils.format_dict(x['dimensions']), 'timestamp': lambda x: format_statistic_timestamp(x['statistics'], x['columns'], 'timestamp'), 'avg': lambda x: format_statistic_value(x['statistics'], x['columns'], 'avg'), 'min': lambda x: format_statistic_value(x['statistics'], x['columns'], 'min'), 'max': lambda x: format_statistic_value(x['statistics'], x['columns'], 'max'), 'count': lambda x: format_statistic_value(x['statistics'], x['columns'], 'count'), 'sum': lambda x: format_statistic_value(x['statistics'], x['columns'], 'sum'), } if isinstance(metric, list): # print the list utils.print_list(metric, cols, formatters=formatters) else: # add the dictionary to a list, so print_list works metric_list = list() metric_list.append(metric) utils.print_list( metric_list, cols, formatters=formatters) @utils.arg('name', metavar='', help='Name of the notification to create.') @utils.arg('type', metavar='', help='The notification type. See monasca notification-type-list for supported types.') @utils.arg('address', metavar='
', help='A valid EMAIL Address, URL, or SERVICE KEY.') @utils.arg('--period', metavar='', type=int, default=0, help='A period for the notification method.') def do_notification_create(mc, args): '''Create notification.''' fields = {} fields['name'] = args.name fields['type'] = args.type fields['address'] = args.address if args.period: fields['period'] = args.period try: notification = mc.notifications.create(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print(jsonutils.dumps(notification, indent=2)) @utils.arg('id', metavar='', help='The ID of the notification.') def do_notification_show(mc, args): '''Describe the notification.''' fields = {} fields['notification_id'] = args.id try: notification = mc.notifications.get(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: if args.json: print(utils.json_formatter(notification)) return formatters = { 'name': utils.json_formatter, 'id': utils.json_formatter, 'type': utils.json_formatter, 'address': utils.json_formatter, 'period': utils.json_formatter, 'links': utils.format_dictlist, } utils.print_dict(notification, formatters=formatters) @utils.arg('--sort-by', metavar='', help='Fields to sort by as a comma separated list. Valid values are id, ' 'name, type, address, created_at, updated_at. ' 'Fields may be followed by "asc" or "desc", ex "address desc", ' 'to set the direction of sorting.') @utils.arg('--offset', metavar='', help='The offset used to paginate the return data.') @utils.arg('--limit', metavar='', help='The amount of data to be returned up to the API maximum limit.') def do_notification_list(mc, args): '''List notifications for this tenant.''' fields = {} if args.limit: fields['limit'] = args.limit if args.offset: fields['offset'] = args.offset if args.sort_by: sort_by = args.sort_by.split(',') for field in sort_by: field_values = field.lower().split() if len(field_values) > 2: print("Invalid sort_by value {}".format(field)) if field_values[0] not in allowed_notification_sort_by: print("Sort-by field name {} is not in [{}]".format(field_values[0], allowed_notification_sort_by)) return if len(field_values) > 1 and field_values[1] not in ['asc', 'desc']: print("Invalid value {}, must be asc or desc".format(field_values[1])) fields['sort_by'] = args.sort_by try: notification = mc.notifications.list(**fields) except osc_exc.ClientException as he: raise osc_exc.CommandError( 'ClientException code=%s message=%s' % (he.code, he.message)) else: if args.json: print(utils.json_formatter(notification)) return cols = ['name', 'id', 'type', 'address', 'period'] formatters = { 'name': lambda x: x['name'], 'id': lambda x: x['id'], 'type': lambda x: x['type'], 'address': lambda x: x['address'], 'period': lambda x: x['period'], } if isinstance(notification, list): utils.print_list( notification, cols, formatters=formatters) else: notif_list = list() notif_list.append(notification) utils.print_list(notif_list, cols, formatters=formatters) @utils.arg('id', metavar='', help='The ID of the notification.') def do_notification_delete(mc, args): '''Delete notification.''' fields = {} fields['notification_id'] = args.id try: mc.notifications.delete(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print('Successfully deleted notification') @utils.arg('id', metavar='', help='The ID of the notification.') @utils.arg('name', metavar='', help='Name of the notification.') @utils.arg('type', metavar='', help='The notification type. See monasca notification-type-list for supported types.') @utils.arg('address', metavar='
', help='A valid EMAIL Address, URL, or SERVICE KEY.') @utils.arg('period', metavar='', type=int, help='A period for the notification method.') def do_notification_update(mc, args): '''Update notification.''' fields = {} fields['notification_id'] = args.id fields['name'] = args.name fields['type'] = args.type fields['address'] = args.address fields['period'] = args.period try: notification = mc.notifications.update(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print(jsonutils.dumps(notification, indent=2)) @utils.arg('id', metavar='', help='The ID of the notification.') @utils.arg('--name', metavar='', help='Name of the notification.') @utils.arg('--type', metavar='', help='The notification type. See monasca notification-type-list for supported types.') @utils.arg('--address', metavar='
', help='A valid EMAIL Address, URL, or SERVICE KEY.') @utils.arg('--period', metavar='', type=int, help='A period for the notification method.') def do_notification_patch(mc, args): '''Patch notification.''' fields = {} fields['notification_id'] = args.id if args.name: fields['name'] = args.name if args.type: fields['type'] = args.type if args.address: fields['address'] = args.address if args.period or args.period == 0: fields['period'] = args.period try: notification = mc.notifications.patch(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print(jsonutils.dumps(notification, indent=2)) def _validate_severity(severity): if severity.upper() not in severity_types: errmsg = ('Invalid severity, not one of [' + ', '.join(severity_types) + ']') print(errmsg) return False return True @utils.arg('name', metavar='', help='Name of the alarm definition to create.') @utils.arg('--description', metavar='', help='Description of the alarm.') @utils.arg('expression', metavar='', help='The alarm expression to evaluate. Quoted.') @utils.arg('--severity', metavar='', help='Severity is one of [LOW, MEDIUM, HIGH, CRITICAL].') @utils.arg('--match-by', metavar='', help='The metric dimensions to use to create unique alarms. ' 'One or more dimension key names separated by a comma. ' 'Key names need quoting when they contain special chars [&,(,),{,},>,<] ' 'that confuse the CLI parser.') @utils.arg('--alarm-actions', metavar='', help='The notification method to use when an alarm state is ALARM. ' 'This param may be specified multiple times.', action='append') @utils.arg('--ok-actions', metavar='', help='The notification method to use when an alarm state is OK. ' 'This param may be specified multiple times.', action='append') @utils.arg('--undetermined-actions', metavar='', help='The notification method to use when an alarm state is ' 'UNDETERMINED. This param may be specified multiple times.', action='append') def do_alarm_definition_create(mc, args): '''Create an alarm definition.''' fields = {} fields['name'] = args.name if args.description: fields['description'] = args.description fields['expression'] = args.expression if args.alarm_actions: fields['alarm_actions'] = args.alarm_actions if args.ok_actions: fields['ok_actions'] = args.ok_actions if args.undetermined_actions: fields['undetermined_actions'] = args.undetermined_actions if args.severity: if not _validate_severity(args.severity): return fields['severity'] = args.severity if args.match_by: fields['match_by'] = args.match_by.split(',') try: alarm = mc.alarm_definitions.create(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print(jsonutils.dumps(alarm, indent=2)) @utils.arg('id', metavar='', help='The ID of the alarm definition.') def do_alarm_definition_show(mc, args): '''Describe the alarm definition.''' fields = {} fields['alarm_id'] = args.id try: alarm = mc.alarm_definitions.get(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: if args.json: print(utils.json_formatter(alarm)) return # print out detail of a single alarm formatters = { 'name': utils.json_formatter, 'id': utils.json_formatter, 'expression': utils.json_formatter, 'expression_data': utils.format_expression_data, 'match_by': utils.json_formatter, 'actions_enabled': utils.json_formatter, 'alarm_actions': utils.json_formatter, 'ok_actions': utils.json_formatter, 'severity': utils.json_formatter, 'undetermined_actions': utils.json_formatter, 'description': utils.json_formatter, 'links': utils.format_dictlist, } utils.print_dict(alarm, formatters=formatters) @utils.arg('--name', metavar='', help='Name of the alarm definition.') @utils.arg('--dimensions', metavar='', help='key value pair used to specify a metric dimension. ' 'This can be specified multiple times, or once with parameters ' 'separated by a comma. ' 'Dimensions need quoting when they contain special chars [&,(,),{,},>,<] ' 'that confuse the CLI parser.', action='append') @utils.arg('--severity', metavar='', help='Severity is one of ["LOW", "MEDIUM", "HIGH", "CRITICAL"].') @utils.arg('--sort-by', metavar='', help='Fields to sort by as a comma separated list. Valid values are id, ' 'name, severity, created_at, updated_at. ' 'Fields may be followed by "asc" or "desc", ex "severity desc", ' 'to set the direction of sorting.') @utils.arg('--offset', metavar='', help='The offset used to paginate the return data.') @utils.arg('--limit', metavar='', help='The amount of data to be returned up to the API maximum limit.') def do_alarm_definition_list(mc, args): '''List alarm definitions for this tenant.''' fields = {} if args.name: fields['name'] = args.name if args.dimensions: fields['dimensions'] = utils.format_dimensions_query(args.dimensions) if args.severity: if not _validate_severity(args.severity): return fields['severity'] = args.severity if args.sort_by: sort_by = args.sort_by.split(',') for field in sort_by: field_values = field.split() if len(field_values) > 2: print("Invalid sort_by value {}".format(field)) if field_values[0] not in allowed_definition_sort_by: print("Sort-by field name {} is not in [{}]".format(field_values[0], allowed_definition_sort_by)) return if len(field_values) > 1 and field_values[1] not in ['asc', 'desc']: print("Invalid value {}, must be asc or desc".format(field_values[1])) fields['sort_by'] = args.sort_by if args.limit: fields['limit'] = args.limit if args.offset: fields['offset'] = args.offset try: alarm = mc.alarm_definitions.list(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: if args.json: print(utils.json_formatter(alarm)) return cols = ['name', 'id', 'expression', 'match_by', 'actions_enabled'] formatters = { 'name': lambda x: x['name'], 'id': lambda x: x['id'], 'expression': lambda x: x['expression'], 'match_by': lambda x: utils.format_list(x['match_by']), 'actions_enabled': lambda x: x['actions_enabled'], } if isinstance(alarm, list): # print the list utils.print_list(alarm, cols, formatters=formatters) else: # add the dictionary to a list, so print_list works alarm_list = list() alarm_list.append(alarm) utils.print_list(alarm_list, cols, formatters=formatters) @utils.arg('id', metavar='', help='The ID of the alarm definition.') def do_alarm_definition_delete(mc, args): '''Delete the alarm definition.''' fields = {} fields['alarm_id'] = args.id try: mc.alarm_definitions.delete(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print('Successfully deleted alarm definition') @utils.arg('id', metavar='', help='The ID of the alarm definition.') @utils.arg('name', metavar='', help='Name of the alarm definition.') @utils.arg('description', metavar='', help='Description of the alarm.') @utils.arg('expression', metavar='', help='The alarm expression to evaluate. Quoted.') @utils.arg('alarm_actions', metavar='', help='The notification method(s) to use when an alarm state is ALARM ' 'as a comma separated list.') @utils.arg('ok_actions', metavar='', help='The notification method(s) to use when an alarm state is OK ' 'as a comma separated list.') @utils.arg('undetermined_actions', metavar='', help='The notification method(s) to use when an alarm state is UNDETERMINED ' 'as a comma separated list.') @utils.arg('actions_enabled', metavar='', help='The actions-enabled boolean is one of [true,false]') @utils.arg('match_by', metavar='', help='The metric dimensions to use to create unique alarms. ' 'One or more dimension key names separated by a comma. ' 'Key names need quoting when they contain special chars [&,(,),{,},>,<] ' 'that confuse the CLI parser.') @utils.arg('severity', metavar='', help='Severity is one of [LOW, MEDIUM, HIGH, CRITICAL].') def do_alarm_definition_update(mc, args): '''Update the alarm definition.''' fields = {} fields['alarm_id'] = args.id fields['name'] = args.name fields['description'] = args.description fields['expression'] = args.expression fields['alarm_actions'] = _arg_split_patch_update(args.alarm_actions) fields['ok_actions'] = _arg_split_patch_update(args.ok_actions) fields['undetermined_actions'] = _arg_split_patch_update(args.undetermined_actions) if args.actions_enabled not in enabled_types: errmsg = ('Invalid value, not one of [' + ', '.join(enabled_types) + ']') print(errmsg) return fields['actions_enabled'] = args.actions_enabled in ['true', 'True'] fields['match_by'] = _arg_split_patch_update(args.match_by) if not _validate_severity(args.severity): return fields['severity'] = args.severity try: alarm = mc.alarm_definitions.update(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print(jsonutils.dumps(alarm, indent=2)) @utils.arg('id', metavar='', help='The ID of the alarm definition.') @utils.arg('--name', metavar='', help='Name of the alarm definition.') @utils.arg('--description', metavar='', help='Description of the alarm.') @utils.arg('--expression', metavar='', help='The alarm expression to evaluate. Quoted.') @utils.arg('--alarm-actions', metavar='', help='The notification method to use when an alarm state is ALARM. ' 'This param may be specified multiple times.', action='append') @utils.arg('--ok-actions', metavar='', help='The notification method to use when an alarm state is OK. ' 'This param may be specified multiple times.', action='append') @utils.arg('--undetermined-actions', metavar='', help='The notification method to use when an alarm state is ' 'UNDETERMINED. This param may be specified multiple times.', action='append') @utils.arg('--actions-enabled', metavar='', help='The actions-enabled boolean is one of [true,false].') @utils.arg('--severity', metavar='', help='Severity is one of [LOW, MEDIUM, HIGH, CRITICAL].') def do_alarm_definition_patch(mc, args): '''Patch the alarm definition.''' fields = {} fields['alarm_id'] = args.id if args.name: fields['name'] = args.name if args.description: fields['description'] = args.description if args.expression: fields['expression'] = args.expression if args.alarm_actions: fields['alarm_actions'] = _arg_split_patch_update(args.alarm_actions, patch=True) if args.ok_actions: fields['ok_actions'] = _arg_split_patch_update(args.ok_actions, patch=True) if args.undetermined_actions: fields['undetermined_actions'] = _arg_split_patch_update(args.undetermined_actions, patch=True) if args.actions_enabled: if args.actions_enabled not in enabled_types: errmsg = ('Invalid value, not one of [' + ', '.join(enabled_types) + ']') print(errmsg) return fields['actions_enabled'] = args.actions_enabled in ['true', 'True'] if args.severity: if not _validate_severity(args.severity): return fields['severity'] = args.severity try: alarm = mc.alarm_definitions.patch(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print(jsonutils.dumps(alarm, indent=2)) @utils.arg('--alarm-definition-id', metavar='', help='The ID of the alarm definition.') @utils.arg('--metric-name', metavar='', help='Name of the metric.') @utils.arg('--metric-dimensions', metavar='', help='key value pair used to specify a metric dimension or ' 'just key to select all values of that dimension.' 'This can be specified multiple times, or once with parameters ' 'separated by a comma. ' 'Dimensions need quoting when they contain special chars [&,(,),{,},>,<] ' 'that confuse the CLI parser.', action='append') @utils.arg('--state', metavar='', help='ALARM_STATE is one of [UNDETERMINED, OK, ALARM].') @utils.arg('--severity', metavar='', help='Severity is one of ["LOW", "MEDIUM", "HIGH", "CRITICAL"].') @utils.arg('--state-updated-start-time', metavar='', help='Return all alarms whose state was updated on or after the time specified.') @utils.arg('--lifecycle-state', metavar='', help='The lifecycle state of the alarm.') @utils.arg('--link', metavar='', help='The link to external data associated with the alarm.') @utils.arg('--sort-by', metavar='', help='Fields to sort by as a comma separated list. Valid values are alarm_id, ' 'alarm_definition_id, state, severity, lifecycle_state, link, ' 'state_updated_timestamp, updated_timestamp, created_timestamp. ' 'Fields may be followed by "asc" or "desc", ex "severity desc", ' 'to set the direction of sorting.') @utils.arg('--offset', metavar='', help='The offset used to paginate the return data.') @utils.arg('--limit', metavar='', help='The amount of data to be returned up to the API maximum limit.') def do_alarm_list(mc, args): '''List alarms for this tenant.''' fields = {} if args.alarm_definition_id: fields['alarm_definition_id'] = args.alarm_definition_id if args.metric_name: fields['metric_name'] = args.metric_name if args.metric_dimensions: fields['metric_dimensions'] = utils.format_dimensions_query(args.metric_dimensions) if args.state: if args.state.upper() not in state_types: errmsg = ('Invalid state, not one of [' + ', '.join(state_types) + ']') print(errmsg) return fields['state'] = args.state if args.severity: if not _validate_severity(args.severity): return fields['severity'] = args.severity if args.state_updated_start_time: fields['state_updated_start_time'] = args.state_updated_start_time if args.lifecycle_state: fields['lifecycle_state'] = args.lifecycle_state if args.link: fields['link'] = args.link if args.limit: fields['limit'] = args.limit if args.offset: fields['offset'] = args.offset if args.sort_by: sort_by = args.sort_by.split(',') for field in sort_by: field_values = field.lower().split() if len(field_values) > 2: print("Invalid sort_by value {}".format(field)) if field_values[0] not in allowed_alarm_sort_by: print("Sort-by field name {} is not in [{}]".format(field_values[0], allowed_alarm_sort_by)) return if len(field_values) > 1 and field_values[1] not in ['asc', 'desc']: print("Invalid value {}, must be asc or desc".format(field_values[1])) fields['sort_by'] = args.sort_by try: alarm = mc.alarms.list(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: if args.json: print(utils.json_formatter(alarm)) return cols = ['id', 'alarm_definition_id', 'alarm_definition_name', 'metric_name', 'metric_dimensions', 'severity', 'state', 'lifecycle_state', 'link', 'state_updated_timestamp', 'updated_timestamp', "created_timestamp"] formatters = { 'id': lambda x: x['id'], 'alarm_definition_id': lambda x: x['alarm_definition']['id'], 'alarm_definition_name': lambda x: x['alarm_definition']['name'], 'metric_name': lambda x: format_metric_name(x['metrics']), 'metric_dimensions': lambda x: format_metric_dimensions(x['metrics']), 'severity': lambda x: x['alarm_definition']['severity'], 'state': lambda x: x['state'], 'lifecycle_state': lambda x: x['lifecycle_state'], 'link': lambda x: x['link'], 'state_updated_timestamp': lambda x: x['state_updated_timestamp'], 'updated_timestamp': lambda x: x['updated_timestamp'], 'created_timestamp': lambda x: x['created_timestamp'], } if isinstance(alarm, list): # print the list utils.print_list(alarm, cols, formatters=formatters) else: # add the dictionary to a list, so print_list works alarm_list = list() alarm_list.append(alarm) utils.print_list(alarm_list, cols, formatters=formatters) @utils.arg('id', metavar='', help='The ID of the alarm.') def do_alarm_show(mc, args): '''Describe the alarm.''' fields = {} fields['alarm_id'] = args.id try: alarm = mc.alarms.get(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: if args.json: print(utils.json_formatter(alarm)) return # print out detail of a single alarm formatters = { 'id': utils.json_formatter, 'alarm_definition': utils.json_formatter, 'metrics': utils.json_formatter, 'state': utils.json_formatter, 'links': utils.format_dictlist, } utils.print_dict(alarm, formatters=formatters) @utils.arg('id', metavar='', help='The ID of the alarm.') @utils.arg('state', metavar='', help='ALARM_STATE is one of [UNDETERMINED, OK, ALARM].') @utils.arg('lifecycle_state', metavar='', help='The lifecycle state of the alarm.') @utils.arg('link', metavar='', help='A link to an external resource with information about the alarm.') def do_alarm_update(mc, args): '''Update the alarm state.''' fields = {} fields['alarm_id'] = args.id if args.state.upper() not in state_types: errmsg = ('Invalid state, not one of [' + ', '.join(state_types) + ']') print(errmsg) return fields['state'] = args.state fields['lifecycle_state'] = args.lifecycle_state fields['link'] = args.link try: alarm = mc.alarms.update(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print(jsonutils.dumps(alarm, indent=2)) @utils.arg('id', metavar='', help='The ID of the alarm.') @utils.arg('--state', metavar='', help='ALARM_STATE is one of [UNDETERMINED, OK, ALARM].') @utils.arg('--lifecycle-state', metavar='', help='The lifecycle state of the alarm.') @utils.arg('--link', metavar='', help='A link to an external resource with information about the alarm.') def do_alarm_patch(mc, args): '''Patch the alarm state.''' fields = {} fields['alarm_id'] = args.id if args.state: if args.state.upper() not in state_types: errmsg = ('Invalid state, not one of [' + ', '.join(state_types) + ']') print(errmsg) return fields['state'] = args.state if args.lifecycle_state: fields['lifecycle_state'] = args.lifecycle_state if args.link: fields['link'] = args.link try: alarm = mc.alarms.patch(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print(jsonutils.dumps(alarm, indent=2)) @utils.arg('id', metavar='', help='The ID of the alarm.') def do_alarm_delete(mc, args): '''Delete the alarm.''' fields = {} fields['alarm_id'] = args.id try: mc.alarms.delete(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: print('Successfully deleted alarm') def output_alarm_history(args, alarm_history): if args.json: print(utils.json_formatter(alarm_history)) return # format output cols = ['alarm_id', 'new_state', 'old_state', 'reason', 'reason_data', 'metric_name', 'metric_dimensions', 'timestamp'] formatters = { 'alarm_id': lambda x: x['alarm_id'], 'new_state': lambda x: x['new_state'], 'old_state': lambda x: x['old_state'], 'reason': lambda x: x['reason'], 'reason_data': lambda x: x['reason_data'], 'metric_name': lambda x: format_metric_name(x['metrics']), 'metric_dimensions': lambda x: format_metric_dimensions(x['metrics']), 'timestamp': lambda x: x['timestamp'], } if isinstance(alarm_history, list): # print the list utils.print_list(alarm_history, cols, formatters=formatters) else: # add the dictionary to a list, so print_list works alarm_list = list() alarm_list.append(alarm_history) utils.print_list(alarm_list, cols, formatters=formatters) @utils.arg('--alarm-definition-id', metavar='', help='The ID of the alarm definition.') @utils.arg('--metric-name', metavar='', help='Name of the metric.') @utils.arg('--metric-dimensions', metavar='', help='key value pair used to specify a metric dimension or ' 'just key to select all values of that dimension.' 'This can be specified multiple times, or once with parameters ' 'separated by a comma. ' 'Dimensions need quoting when they contain special chars [&,(,),{,},>,<] ' 'that confuse the CLI parser.', action='append') @utils.arg('--state', metavar='', help='ALARM_STATE is one of [UNDETERMINED, OK, ALARM].') @utils.arg('--severity', metavar='', help='Severity is one of ["LOW", "MEDIUM", "HIGH", "CRITICAL"].') @utils.arg('--state-updated-start-time', metavar='', help='Return all alarms whose state was updated on or after the time specified.') @utils.arg('--lifecycle-state', metavar='', help='The lifecycle state of the alarm.') @utils.arg('--link', metavar='', help='The link to external data associated with the alarm.') @utils.arg('--group-by', metavar='', help='Comma separated list of one or more fields to group the results by. ' 'Group by is one or more of [alarm_definition_id, name, state, link, ' 'lifecycle_state, metric_name, dimension_name, dimension_value].') @utils.arg('--offset', metavar='', help='The offset used to paginate the return data.') @utils.arg('--limit', metavar='', help='The amount of data to be returned up to the API maximum limit.') def do_alarm_count(mc, args): '''Count alarms.''' fields = {} if args.alarm_definition_id: fields['alarm_definition_id'] = args.alarm_definition_id if args.metric_name: fields['metric_name'] = args.metric_name if args.metric_dimensions: fields['metric_dimensions'] = utils.format_dimensions_query(args.metric_dimensions) if args.state: if args.state.upper() not in state_types: errmsg = ('Invalid state, not one of [' + ', '.join(state_types) + ']') print(errmsg) return fields['state'] = args.state if args.severity: if not _validate_severity(args.severity): return fields['severity'] = args.severity if args.state_updated_start_time: fields['state_updated_start_time'] = args.state_updated_start_time if args.lifecycle_state: fields['lifecycle_state'] = args.lifecycle_state if args.link: fields['link'] = args.link if args.group_by: group_by = args.group_by.split(',') if not set(group_by).issubset(set(group_by_types)): errmsg = ('Invalid group-by, one or more values not in [' + ','.join(group_by_types) + ']') print(errmsg) return fields['group_by'] = args.group_by if args.limit: fields['limit'] = args.limit if args.offset: fields['offset'] = args.offset try: counts = mc.alarms.count(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: if args.json: print(utils.json_formatter(counts)) return cols = counts['columns'] utils.print_list(counts['counts'], [i for i in range(len(cols))], field_labels=cols) @utils.arg('id', metavar='', help='The ID of the alarm.') @utils.arg('--offset', metavar='', help='The offset used to paginate the return data.') @utils.arg('--limit', metavar='', help='The amount of data to be returned up to the API maximum limit.') def do_alarm_history(mc, args): '''Alarm state transition history.''' fields = {} fields['alarm_id'] = args.id if args.limit: fields['limit'] = args.limit if args.offset: fields['offset'] = args.offset try: alarm = mc.alarms.history(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: output_alarm_history(args, alarm) @utils.arg('--dimensions', metavar='', help='key value pair used to specify a metric dimension. ' 'This can be specified multiple times, or once with parameters ' 'separated by a comma. ' 'Dimensions need quoting when they contain special chars [&,(,),{,},>,<] ' 'that confuse the CLI parser.', action='append') @utils.arg('--starttime', metavar='', help='measurements >= UTC time. format: 2014-01-01T00:00:00Z. OR' ' format: -120 (previous 120 minutes).') @utils.arg('--endtime', metavar='', help='measurements <= UTC time. format: 2014-01-01T00:00:00Z.') @utils.arg('--offset', metavar='', help='The offset used to paginate the return data.') @utils.arg('--limit', metavar='', help='The amount of data to be returned up to the API maximum limit.') def do_alarm_history_list(mc, args): '''List alarms state history.''' fields = {} if args.dimensions: fields['dimensions'] = utils.format_parameters(args.dimensions) if args.starttime: _translate_starttime(args) fields['start_time'] = args.starttime if args.endtime: fields['end_time'] = args.endtime if args.limit: fields['limit'] = args.limit if args.offset: fields['offset'] = args.offset try: alarm = mc.alarms.history_list(**fields) except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: output_alarm_history(args, alarm) def do_notification_type_list(mc, args): '''List notification types supported by monasca.''' try: notification_types = mc.notificationtypes.list() except (osc_exc.ClientException, k_exc.HttpError) as he: raise osc_exc.CommandError('%s\n%s' % (he.message, he.details)) else: if args.json: print(utils.json_formatter(notification_types)) return else: formatters = {'types': lambda x: x["type"]} # utils.print_list(notification_types['types'], ["types"], formatters=formatters) utils.print_list(notification_types, ["types"], formatters=formatters) def _translate_starttime(args): if args.starttime[0] == '-': deltaT = time.time() + (int(args.starttime) * 60) utc = str(datetime.datetime.utcfromtimestamp(deltaT)) utc = utc.replace(" ", "T")[:-7] + 'Z' args.starttime = utc def _arg_split_patch_update(arg, patch=False): if patch: arg = ','.join(arg) if not arg or arg == "[]": arg_split = [] else: arg_split = arg.split(',') return arg_split