limit alarm actions
Currently we have alarm quotas, but user still can create a single alarm with thousands of actions, the worse thing is that normal user can create log:// and test:// notifiers which they could never use it, but by doing such thing, normal user can break down ceilometer services, then maybe the whole server. That is because log:// will log to local disk (by default) and test:// will continuously consume server's memory. This patch adds a configure option to let cloud administrator to limit each alarm's maximum actions for three states, and disables normal user to create log:// and test:// to avoid bad things happen. Change-Id: I7325ab72c94d307075f1317ee0b7b19f30a5d231 Closes-Bug: #1408248 DocImpact
This commit is contained in:
@@ -64,6 +64,10 @@ ALARM_API_OPTS = [
|
||||
default=None,
|
||||
help='Maximum number of alarms defined for a project.'
|
||||
),
|
||||
cfg.IntOpt('alarm_max_actions',
|
||||
default=-1,
|
||||
help='Maximum count of actions for each state of an alarm, '
|
||||
'non-positive number means no limit.'),
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(ALARM_API_OPTS, group='alarm')
|
||||
@@ -306,12 +310,28 @@ class Alarm(base.Base):
|
||||
@staticmethod
|
||||
def check_alarm_actions(alarm):
|
||||
actions_schema = ceilometer_alarm.NOTIFIER_SCHEMAS
|
||||
max_actions = cfg.CONF.alarm.alarm_max_actions
|
||||
for state in state_kind:
|
||||
actions_name = state.replace(" ", "_") + '_actions'
|
||||
actions = getattr(alarm, actions_name)
|
||||
if not actions:
|
||||
continue
|
||||
|
||||
action_set = set(actions)
|
||||
if len(actions) != len(action_set):
|
||||
LOG.info(_('duplicate actions are found: %s, '
|
||||
'remove duplicate ones') % actions)
|
||||
actions = list(action_set)
|
||||
setattr(alarm, actions_name, actions)
|
||||
|
||||
if 0 < max_actions < len(actions):
|
||||
error = _('%(name)s count exceeds maximum value '
|
||||
'%(maximum)d') % {"name": actions_name,
|
||||
"maximum": max_actions}
|
||||
raise base.ClientSideError(error)
|
||||
|
||||
limited = rbac.get_limited_to_project(pecan.request.headers)
|
||||
|
||||
for action in actions:
|
||||
try:
|
||||
url = netutils.urlsplit(action)
|
||||
@@ -321,6 +341,10 @@ class Alarm(base.Base):
|
||||
if url.scheme not in actions_schema:
|
||||
error = _("Unsupported action %s") % action
|
||||
raise base.ClientSideError(error)
|
||||
if limited and url.scheme in ('log', 'test'):
|
||||
error = _('You are not authorized to create '
|
||||
'action: %s') % action
|
||||
raise base.ClientSideError(error, status_code=401)
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
|
||||
@@ -1709,6 +1709,88 @@ class TestAlarms(v2.FunctionalTest,
|
||||
self._test_post_alarm_combination_rule_less_than_two_alarms(['a',
|
||||
'a'])
|
||||
|
||||
def test_post_alarm_with_duplicate_actions(self):
|
||||
body = {
|
||||
'name': 'dup-alarm-actions',
|
||||
'type': 'combination',
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a', 'b'],
|
||||
},
|
||||
'alarm_actions': ['http://no.where', 'http://no.where']
|
||||
}
|
||||
resp = self.post_json('/alarms', params=body,
|
||||
headers=self.auth_headers)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
alarms = list(self.alarm_conn.get_alarms(name='dup-alarm-actions'))
|
||||
self.assertEqual(1, len(alarms))
|
||||
self.assertEqual(['http://no.where'], alarms[0].alarm_actions)
|
||||
|
||||
def test_post_alarm_with_too_many_actions(self):
|
||||
self.CONF.set_override('alarm_max_actions', 1, group='alarm')
|
||||
body = {
|
||||
'name': 'alarm-with-many-actions',
|
||||
'type': 'combination',
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a', 'b'],
|
||||
},
|
||||
'alarm_actions': ['http://no.where', 'http://no.where2']
|
||||
}
|
||||
resp = self.post_json('/alarms', params=body, expect_errors=True,
|
||||
headers=self.auth_headers)
|
||||
self.assertEqual(400, resp.status_code)
|
||||
self.assertEqual("alarm_actions count exceeds maximum value 1",
|
||||
resp.json['error_message']['faultstring'])
|
||||
|
||||
def test_post_alarm_normal_user_set_log_actions(self):
|
||||
body = {
|
||||
'name': 'log_alarm_actions',
|
||||
'type': 'combination',
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a', 'b'],
|
||||
},
|
||||
'alarm_actions': ['log://']
|
||||
}
|
||||
resp = self.post_json('/alarms', params=body, expect_errors=True,
|
||||
headers=self.auth_headers)
|
||||
self.assertEqual(401, resp.status_code)
|
||||
expected_msg = ("You are not authorized to create action: log://")
|
||||
self.assertEqual(expected_msg,
|
||||
resp.json['error_message']['faultstring'])
|
||||
|
||||
def test_post_alarm_normal_user_set_test_actions(self):
|
||||
body = {
|
||||
'name': 'test_alarm_actions',
|
||||
'type': 'combination',
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a', 'b'],
|
||||
},
|
||||
'alarm_actions': ['test://']
|
||||
}
|
||||
resp = self.post_json('/alarms', params=body, expect_errors=True,
|
||||
headers=self.auth_headers)
|
||||
self.assertEqual(401, resp.status_code)
|
||||
expected_msg = ("You are not authorized to create action: test://")
|
||||
self.assertEqual(expected_msg,
|
||||
resp.json['error_message']['faultstring'])
|
||||
|
||||
def test_post_alarm_admin_user_set_log_test_actions(self):
|
||||
body = {
|
||||
'name': 'admin_alarm_actions',
|
||||
'type': 'combination',
|
||||
'combination_rule': {
|
||||
'alarm_ids': ['a', 'b'],
|
||||
},
|
||||
'alarm_actions': ['test://', 'log://']
|
||||
}
|
||||
headers = self.auth_headers
|
||||
headers['X-Roles'] = 'admin'
|
||||
self.post_json('/alarms', params=body, status=201,
|
||||
headers=headers)
|
||||
alarms = list(self.alarm_conn.get_alarms(name='admin_alarm_actions'))
|
||||
self.assertEqual(1, len(alarms))
|
||||
self.assertEqual(['test://', 'log://'],
|
||||
alarms[0].alarm_actions)
|
||||
|
||||
def test_put_alarm(self):
|
||||
json = {
|
||||
'enabled': False,
|
||||
|
||||
Reference in New Issue
Block a user