Support combination alarms to composite alarms conversion
This change adds a tool for converting combination alarms to composite alarms. Change-Id: Iafcb65cd3686a628855a1e52b0eef86f641d295c
This commit is contained in:
parent
77238e3367
commit
050a7dcb34
146
aodh/cmd/alarm_conversion.py
Normal file
146
aodh/cmd/alarm_conversion.py
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""A tool for converting combination alarms to composite alarms.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from oslo_log import log
|
||||||
|
|
||||||
|
from aodh.i18n import _LI, _LW
|
||||||
|
from aodh import service
|
||||||
|
from aodh import storage
|
||||||
|
from aodh.storage import models
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DependentAlarmNotFound(Exception):
|
||||||
|
"""The dependent alarm is not found."""
|
||||||
|
|
||||||
|
def __init__(self, com_alarm_id, dependent_alarm_id):
|
||||||
|
self.com_alarm_id = com_alarm_id
|
||||||
|
self.dependent_alarm_id = dependent_alarm_id
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedSubAlarmType(Exception):
|
||||||
|
"""Unsupported sub-alarm type."""
|
||||||
|
|
||||||
|
def __init__(self, sub_alarm_id, sub_alarm_type):
|
||||||
|
self.sub_alarm_id = sub_alarm_id
|
||||||
|
self.sub_alarm_type = sub_alarm_type
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_composite_rule(conn, combin_alarm):
|
||||||
|
alarm_ids = combin_alarm.rule['alarm_ids']
|
||||||
|
com_op = combin_alarm.rule['operator']
|
||||||
|
LOG.info(_LI('Start converting combination alarm %(alarm)s, it depends on '
|
||||||
|
'alarms: %(alarm_ids)s'),
|
||||||
|
{'alarm': combin_alarm.alarm_id, 'alarm_ids': str(alarm_ids)})
|
||||||
|
threshold_rules = []
|
||||||
|
for alarm_id in alarm_ids:
|
||||||
|
try:
|
||||||
|
sub_alarm = list(conn.get_alarms(alarm_id=alarm_id))[0]
|
||||||
|
except IndexError:
|
||||||
|
raise DependentAlarmNotFound(combin_alarm.alarm_id, alarm_id)
|
||||||
|
if sub_alarm.type in ('threshold', 'gnocchi_resources_threshold',
|
||||||
|
'gnocchi_aggregation_by_metrics_threshold',
|
||||||
|
'gnocchi_aggregation_by_resources_threshold'):
|
||||||
|
sub_alarm.rule.update(type=sub_alarm.type)
|
||||||
|
threshold_rules.append(sub_alarm.rule)
|
||||||
|
elif sub_alarm.type == 'combination':
|
||||||
|
threshold_rules.append(_generate_composite_rule(conn, sub_alarm))
|
||||||
|
else:
|
||||||
|
raise UnsupportedSubAlarmType(alarm_id, sub_alarm.type)
|
||||||
|
else:
|
||||||
|
return {com_op: threshold_rules}
|
||||||
|
|
||||||
|
|
||||||
|
def get_parser():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='for converting combination alarms to composite alarms.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--delete-combination-alarm',
|
||||||
|
default=False,
|
||||||
|
type=bool,
|
||||||
|
help='Delete the combination alarm when conversion is done.',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--alarm-id',
|
||||||
|
default=None,
|
||||||
|
type=str,
|
||||||
|
help='Only convert the alarm specified by this option.',
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def conversion():
|
||||||
|
confirm = raw_input("This tool is used for converting the combination "
|
||||||
|
"alarms to composite alarms, please type 'yes' to "
|
||||||
|
"confirm: ")
|
||||||
|
if confirm != 'yes':
|
||||||
|
print("Alarm conversion aborted!")
|
||||||
|
return
|
||||||
|
args = get_parser().parse_args()
|
||||||
|
conf = service.prepare_service()
|
||||||
|
conn = storage.get_connection_from_config(conf)
|
||||||
|
combination_alarms = list(conn.get_alarms(alarm_type='combination',
|
||||||
|
alarm_id=args.alarm_id or None))
|
||||||
|
count = 0
|
||||||
|
for alarm in combination_alarms:
|
||||||
|
new_name = 'From-combination: %s' % alarm.alarm_id
|
||||||
|
n_alarm = list(conn.get_alarms(name=new_name, alarm_type='composite'))
|
||||||
|
if n_alarm:
|
||||||
|
LOG.warning(_LW('Alarm %(alarm)s has been already converted as '
|
||||||
|
'composite alarm: %(n_alarm_id)s, skipped.'),
|
||||||
|
{'alarm': alarm.alarm_id,
|
||||||
|
'n_alarm_id': n_alarm[0].alarm_id})
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
composite_rule = _generate_composite_rule(conn, alarm)
|
||||||
|
except DependentAlarmNotFound as e:
|
||||||
|
LOG.warning(_LW('The dependent alarm %(dep_alarm)s of alarm %'
|
||||||
|
'(com_alarm)s not found, skipped.'),
|
||||||
|
{'com_alarm': e.com_alarm_id,
|
||||||
|
'dep_alarm': e.dependent_alarm_id})
|
||||||
|
continue
|
||||||
|
except UnsupportedSubAlarmType as e:
|
||||||
|
LOG.warning(_LW('Alarm conversion from combination to composite '
|
||||||
|
'only support combination alarms depending '
|
||||||
|
'threshold alarms, the type of alarm %(alarm)s '
|
||||||
|
'is: %(type)s, skipped.'),
|
||||||
|
{'alarm': e.sub_alarm_id, 'type': e.sub_alarm_type})
|
||||||
|
continue
|
||||||
|
new_alarm = models.Alarm(**alarm.as_dict())
|
||||||
|
new_alarm.alarm_id = str(uuid.uuid4())
|
||||||
|
new_alarm.name = new_name
|
||||||
|
new_alarm.type = 'composite'
|
||||||
|
new_alarm.description = ('composite alarm converted from combination '
|
||||||
|
'alarm: %s' % alarm.alarm_id)
|
||||||
|
new_alarm.rule = composite_rule
|
||||||
|
new_alarm.timestamp = datetime.datetime.now()
|
||||||
|
conn.create_alarm(new_alarm)
|
||||||
|
LOG.info(_LI('End Converting combination alarm %(s_alarm)s to '
|
||||||
|
'composite alarm %(d_alarm)s'),
|
||||||
|
{'s_alarm': alarm.alarm_id, 'd_alarm': new_alarm.alarm_id})
|
||||||
|
count += 1
|
||||||
|
if args.delete_combination_alarm:
|
||||||
|
for alarm in combination_alarms:
|
||||||
|
LOG.info(_LI('Deleting the combination alarm %s...'),
|
||||||
|
alarm.alarm_id)
|
||||||
|
conn.delete_alarm(alarm.alarm_id)
|
||||||
|
LOG.info(_LI('%s combination alarms have been converted to composite '
|
||||||
|
'alarms.'), count)
|
@ -25,6 +25,7 @@ from oslo_serialization import jsonutils
|
|||||||
import six
|
import six
|
||||||
from six import moves
|
from six import moves
|
||||||
|
|
||||||
|
from aodh.cmd import alarm_conversion
|
||||||
from aodh import messaging
|
from aodh import messaging
|
||||||
from aodh.storage import models
|
from aodh.storage import models
|
||||||
from aodh.tests import constants
|
from aodh.tests import constants
|
||||||
@ -3383,3 +3384,66 @@ class TestPaginationQuery(TestAlarmsBase):
|
|||||||
key=lambda d: (d['event_id'], d['timestamp']),
|
key=lambda d: (d['event_id'], d['timestamp']),
|
||||||
reverse=True)
|
reverse=True)
|
||||||
self.assertEqual(sorted_data, data)
|
self.assertEqual(sorted_data, data)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCombinationCompositeConversion(TestAlarmsBase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestCombinationCompositeConversion, self).setUp()
|
||||||
|
alarms = default_alarms(self.auth_headers)
|
||||||
|
for alarm in alarms:
|
||||||
|
self.alarm_conn.create_alarm(alarm)
|
||||||
|
com_parameters = alarms[3].as_dict()
|
||||||
|
com_parameters.update(dict(name='name5', alarm_id='e', description='e',
|
||||||
|
rule=dict(alarm_ids=['b', 'c'],
|
||||||
|
operator='and')))
|
||||||
|
combin1 = models.Alarm(**com_parameters)
|
||||||
|
self.alarm_conn.create_alarm(combin1)
|
||||||
|
com_parameters.update(dict(name='name6', alarm_id='f', description='f',
|
||||||
|
rule=dict(alarm_ids=['d', 'e'],
|
||||||
|
operator='and')))
|
||||||
|
combin2 = models.Alarm(**com_parameters)
|
||||||
|
self.alarm_conn.create_alarm(combin2)
|
||||||
|
|
||||||
|
def test_conversion_without_combination_deletion(self):
|
||||||
|
data = self.get_json('/alarms', headers=self.auth_headers)
|
||||||
|
self.assertEqual(6, len(data))
|
||||||
|
url = '/alarms?q.field=type&q.op=eq&q.value=combination'
|
||||||
|
combination_alarms = self.get_json(url, headers=self.auth_headers)
|
||||||
|
self.assertEqual(3, len(combination_alarms))
|
||||||
|
test_args = alarm_conversion.get_parser().parse_args([])
|
||||||
|
with mock.patch('__builtin__.raw_input', return_value='yes'):
|
||||||
|
with mock.patch('argparse.ArgumentParser.parse_args',
|
||||||
|
return_value=test_args):
|
||||||
|
alarm_conversion.conversion()
|
||||||
|
url = '/alarms?q.field=type&q.op=eq&q.value=composite'
|
||||||
|
composite_alarms = self.get_json(url, headers=self.auth_headers)
|
||||||
|
self.assertEqual(3, len(composite_alarms))
|
||||||
|
url = '/alarms?q.field=type&q.op=eq&q.value=combination'
|
||||||
|
combination_alarms = self.get_json(url, headers=self.auth_headers)
|
||||||
|
self.assertEqual(3, len(combination_alarms))
|
||||||
|
|
||||||
|
def test_conversion_with_combination_deletion(self):
|
||||||
|
test_args = alarm_conversion.get_parser().parse_args(
|
||||||
|
['--delete-combination-alarm', 'True'])
|
||||||
|
with mock.patch('__builtin__.raw_input', return_value='yes'):
|
||||||
|
with mock.patch('argparse.ArgumentParser.parse_args',
|
||||||
|
return_value=test_args):
|
||||||
|
alarm_conversion.conversion()
|
||||||
|
url = '/alarms?q.field=type&q.op=eq&q.value=composite'
|
||||||
|
composite_alarms = self.get_json(url, headers=self.auth_headers)
|
||||||
|
self.assertEqual(3, len(composite_alarms))
|
||||||
|
url = '/alarms?q.field=type&q.op=eq&q.value=combination'
|
||||||
|
combination_alarms = self.get_json(url, headers=self.auth_headers)
|
||||||
|
self.assertEqual(0, len(combination_alarms))
|
||||||
|
|
||||||
|
def test_conversion_with_alarm_specified(self):
|
||||||
|
test_args = alarm_conversion.get_parser().parse_args(
|
||||||
|
['--alarm-id', 'e'])
|
||||||
|
with mock.patch('__builtin__.raw_input', return_value='yes'):
|
||||||
|
with mock.patch('argparse.ArgumentParser.parse_args',
|
||||||
|
return_value=test_args):
|
||||||
|
alarm_conversion.conversion()
|
||||||
|
url = '/alarms?q.field=type&q.op=eq&q.value=composite'
|
||||||
|
composite_alarms = self.get_json(url, headers=self.auth_headers)
|
||||||
|
self.assertEqual(1, len(composite_alarms))
|
||||||
|
self.assertEqual('From-combination: e', composite_alarms[0]['name'])
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
upgrade:
|
||||||
|
- >
|
||||||
|
Add a tool for converting combination alarms to composite alarms,
|
||||||
|
since we have deprecated the combination alarm support and recommend
|
||||||
|
to use composite alarm to perform multiple conditions alarming.
|
@ -115,6 +115,7 @@ console_scripts =
|
|||||||
aodh-notifier = aodh.cmd.alarm:notifier
|
aodh-notifier = aodh.cmd.alarm:notifier
|
||||||
aodh-listener = aodh.cmd.alarm:listener
|
aodh-listener = aodh.cmd.alarm:listener
|
||||||
aodh-data-migration = aodh.cmd.data_migration:main
|
aodh-data-migration = aodh.cmd.data_migration:main
|
||||||
|
aodh-combination-alarm-conversion = aodh.cmd.alarm_conversion:conversion
|
||||||
|
|
||||||
oslo.config.opts =
|
oslo.config.opts =
|
||||||
aodh = aodh.opts:list_opts
|
aodh = aodh.opts:list_opts
|
||||||
|
Loading…
Reference in New Issue
Block a user