add alarm interface
this adds functionality to support ceilometer+aodh. it supports: - aodh alarm list - aodh alarm show - aodh alarm search - aodh alarm create - aodh alarm update - aodh alarm delete Change-Id: I50a3578fed543185094e0ae545a09ca72039372e
This commit is contained in:
@@ -25,12 +25,19 @@ from keystoneauth1 import loading
|
|||||||
|
|
||||||
from aodhclient import client
|
from aodhclient import client
|
||||||
from aodhclient import noauth
|
from aodhclient import noauth
|
||||||
|
from aodhclient.v2 import alarm_cli
|
||||||
from aodhclient.v2 import capabilities_cli
|
from aodhclient.v2 import capabilities_cli
|
||||||
from aodhclient.version import __version__
|
from aodhclient.version import __version__
|
||||||
|
|
||||||
|
|
||||||
class AodhCommandManager(commandmanager.CommandManager):
|
class AodhCommandManager(commandmanager.CommandManager):
|
||||||
SHELL_COMMANDS = {
|
SHELL_COMMANDS = {
|
||||||
|
"alarm create": alarm_cli.CliAlarmCreate,
|
||||||
|
"alarm delete": alarm_cli.CliAlarmDelete,
|
||||||
|
"alarm list": alarm_cli.CliAlarmList,
|
||||||
|
"alarm show": alarm_cli.CliAlarmShow,
|
||||||
|
"alarm search": alarm_cli.CliAlarmSearch,
|
||||||
|
"alarm update": alarm_cli.CliAlarmUpdate,
|
||||||
"capabilities list": capabilities_cli.CliCapabilitiesList,
|
"capabilities list": capabilities_cli.CliCapabilitiesList,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
112
aodhclient/tests/functional/test_alarm.py
Normal file
112
aodhclient/tests/functional/test_alarm.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# 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 uuid
|
||||||
|
|
||||||
|
from aodhclient.tests.functional import base
|
||||||
|
|
||||||
|
|
||||||
|
class AodhClientTest(base.ClientTestBase):
|
||||||
|
|
||||||
|
def test_help(self):
|
||||||
|
self.aodh("help", params="alarm create")
|
||||||
|
self.aodh("help", params="alarm delete")
|
||||||
|
self.aodh("help", params="alarm list")
|
||||||
|
self.aodh("help", params="alarm search")
|
||||||
|
self.aodh("help", params="alarm show")
|
||||||
|
self.aodh("help", params="alarm update")
|
||||||
|
|
||||||
|
def test_alarm_scenario(self):
|
||||||
|
|
||||||
|
PROJECT_ID = str(uuid.uuid4())
|
||||||
|
|
||||||
|
# CREATE
|
||||||
|
result = self.aodh(u'alarm',
|
||||||
|
params=(u"create --name alarm1 -m meter_name "
|
||||||
|
" --threshold 5 "
|
||||||
|
"--project-id %s" % PROJECT_ID))
|
||||||
|
alarm = self.details_multiple(result)[0]
|
||||||
|
ALARM_ID = alarm['alarm_id']
|
||||||
|
self.assertEqual('alarm1', alarm['name'])
|
||||||
|
self.assertEqual('meter_name', alarm['meter_name'])
|
||||||
|
self.assertEqual('5.0', alarm['threshold'])
|
||||||
|
|
||||||
|
# CREATE FAIL
|
||||||
|
result = self.aodh(u'alarm',
|
||||||
|
params=(u"create --name alarm1 -m meter_name "
|
||||||
|
" --threshold 5 "
|
||||||
|
"--project-id %s" % PROJECT_ID),
|
||||||
|
fail_ok=True, merge_stderr=True)
|
||||||
|
self.assertEqual(result.strip(), 'Conflict (HTTP 409)')
|
||||||
|
|
||||||
|
# UPDATE
|
||||||
|
result = self.aodh(
|
||||||
|
'alarm', params=("update %s --severity critical --threshold 10"
|
||||||
|
% ALARM_ID))
|
||||||
|
alarm_updated = self.details_multiple(result)[0]
|
||||||
|
self.assertEqual(ALARM_ID, alarm_updated["alarm_id"])
|
||||||
|
self.assertEqual('critical', alarm_updated['severity'])
|
||||||
|
self.assertEqual('10.0', alarm_updated["threshold"])
|
||||||
|
|
||||||
|
# GET
|
||||||
|
result = self.aodh(
|
||||||
|
'alarm', params="show %s" % ALARM_ID)
|
||||||
|
alarm_show = self.details_multiple(result)[0]
|
||||||
|
self.assertEqual(ALARM_ID, alarm_show["alarm_id"])
|
||||||
|
self.assertEqual(PROJECT_ID, alarm_show["project_id"])
|
||||||
|
self.assertEqual('alarm1', alarm_show['name'])
|
||||||
|
self.assertEqual('meter_name', alarm_show['meter_name'])
|
||||||
|
self.assertEqual('10.0', alarm_show['threshold'])
|
||||||
|
|
||||||
|
# LIST
|
||||||
|
result = self.aodh('alarm', params="list")
|
||||||
|
self.assertIn(ALARM_ID,
|
||||||
|
[r['alarm_id'] for r in self.parser.listing(result)])
|
||||||
|
for alarm_list in self.parser.listing(result):
|
||||||
|
if alarm_list["alarm_id"] == ALARM_ID:
|
||||||
|
self.assertEqual('alarm1', alarm_list['name'])
|
||||||
|
|
||||||
|
# SEARCH ALL
|
||||||
|
result = self.aodh('alarm', params=("search"))
|
||||||
|
self.assertIn(ALARM_ID,
|
||||||
|
[r['alarm_id'] for r in self.parser.listing(result)])
|
||||||
|
for alarm_list in self.parser.listing(result):
|
||||||
|
if alarm_list["alarm_id"] == ALARM_ID:
|
||||||
|
self.assertEqual('alarm1', alarm_list['name'])
|
||||||
|
|
||||||
|
# SEARCH SOME
|
||||||
|
result = self.aodh('alarm',
|
||||||
|
params=("search --query "
|
||||||
|
"'{\"=\": {\"project_id\": \"%s\"}}'"
|
||||||
|
% PROJECT_ID))
|
||||||
|
alarm_list = self.parser.listing(result)[0]
|
||||||
|
self.assertEqual(ALARM_ID, alarm_list["alarm_id"])
|
||||||
|
self.assertEqual('alarm1', alarm_list['name'])
|
||||||
|
|
||||||
|
# DELETE
|
||||||
|
result = self.aodh('alarm', params="delete %s" % ALARM_ID)
|
||||||
|
self.assertEqual("", result)
|
||||||
|
|
||||||
|
# GET FAIL
|
||||||
|
result = self.aodh('alarm', params="show %s" % ALARM_ID,
|
||||||
|
fail_ok=True, merge_stderr=True)
|
||||||
|
self.assertEqual(result.strip(), "Not found (HTTP 404)")
|
||||||
|
|
||||||
|
# DELETE FAIL
|
||||||
|
result = self.aodh('alarm', params="delete %s" % ALARM_ID,
|
||||||
|
fail_ok=True, merge_stderr=True)
|
||||||
|
self.assertEqual(result.strip(), "Not found (HTTP 404)")
|
||||||
|
|
||||||
|
# LIST DOES NOT HAVE ALARM
|
||||||
|
result = self.aodh('alarm', params="list")
|
||||||
|
self.assertNotIn(ALARM_ID,
|
||||||
|
[r['alarm_id'] for r in self.parser.listing(result)])
|
||||||
80
aodhclient/v2/alarm.py
Normal file
80
aodhclient/v2/alarm.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#
|
||||||
|
# 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 oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
from aodhclient.v2 import base
|
||||||
|
|
||||||
|
|
||||||
|
class AlarmManager(base.Manager):
|
||||||
|
|
||||||
|
url = "v2/alarms"
|
||||||
|
|
||||||
|
def list(self):
|
||||||
|
"""List alarms"""
|
||||||
|
return self._get(self.url).json()
|
||||||
|
|
||||||
|
def get(self, alarm_id):
|
||||||
|
"""Get an alarm
|
||||||
|
|
||||||
|
:param alarm_id: ID of the alarm
|
||||||
|
:type alarm_id: str
|
||||||
|
"""
|
||||||
|
return self._get(self.url + '/' + alarm_id).json()
|
||||||
|
|
||||||
|
def create(self, alarm):
|
||||||
|
"""Create an alarm
|
||||||
|
|
||||||
|
:param alarm: the alarm
|
||||||
|
:type alarm: dict
|
||||||
|
"""
|
||||||
|
return self._post(
|
||||||
|
self.url, headers={'Content-Type': "application/json"},
|
||||||
|
data=jsonutils.dumps(alarm)).json()
|
||||||
|
|
||||||
|
def update(self, alarm_id, alarm_update):
|
||||||
|
"""Update an alarm
|
||||||
|
|
||||||
|
:param alarm_id: ID of the alarm
|
||||||
|
:type alarm_id: str
|
||||||
|
:param attributes: Attributes of the alarm
|
||||||
|
:type attributes: dict
|
||||||
|
"""
|
||||||
|
alarm = self._get(self.url + '/' + alarm_id).json()
|
||||||
|
if alarm_update.get('threshold_rule'):
|
||||||
|
alarm['threshold_rule'].update(alarm_update.get('threshold_rule'))
|
||||||
|
alarm_update.pop('threshold_rule')
|
||||||
|
alarm.update(alarm_update)
|
||||||
|
return self._put(
|
||||||
|
self.url + '/' + alarm_id,
|
||||||
|
headers={'Content-Type': "application/json"},
|
||||||
|
data=jsonutils.dumps(alarm)).json()
|
||||||
|
|
||||||
|
def delete(self, alarm_id):
|
||||||
|
"""Delete an alarm
|
||||||
|
|
||||||
|
:param alarm_id: ID of the alarm
|
||||||
|
:type alarm_id: str
|
||||||
|
"""
|
||||||
|
self._delete(self.url + '/' + alarm_id)
|
||||||
|
|
||||||
|
def search(self, query=None):
|
||||||
|
"""List alarms
|
||||||
|
|
||||||
|
:param query: The query dictionary
|
||||||
|
:type query: dict
|
||||||
|
"""
|
||||||
|
query = {'filter': query} if query else {}
|
||||||
|
url = "v2/query/alarms"
|
||||||
|
return self._post(url, headers={'Content-Type': "application/json"},
|
||||||
|
data=jsonutils.dumps(query)).json()
|
||||||
202
aodhclient/v2/alarm_cli.py
Normal file
202
aodhclient/v2/alarm_cli.py
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
#
|
||||||
|
# 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_utils import strutils
|
||||||
|
|
||||||
|
from aodhclient import utils
|
||||||
|
|
||||||
|
|
||||||
|
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']
|
||||||
|
|
||||||
|
|
||||||
|
class CliAlarmList(lister.Lister):
|
||||||
|
"""List alarms"""
|
||||||
|
|
||||||
|
COLS = ('alarm_id', 'name', 'state', 'severity', 'enabled',
|
||||||
|
'repeat_actions', 'threshold_rule', 'time_constraints')
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
alarms = self.app.client.alarm.list()
|
||||||
|
return utils.list2cols(self.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):
|
||||||
|
alarms = self.app.client.alarm.search(query=parsed_args.query)
|
||||||
|
return utils.list2cols(self.COLS, alarms)
|
||||||
|
|
||||||
|
|
||||||
|
def _format_alarm(alarm):
|
||||||
|
alarm.update(alarm.pop('threshold_rule'))
|
||||||
|
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('--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>',
|
||||||
|
help='State of the alarm, one of: '
|
||||||
|
+ str(ALARM_STATES))
|
||||||
|
parser.add_argument('--severity', metavar='<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/actioning 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'))
|
||||||
|
parser.add_argument(
|
||||||
|
'-m', '--meter-name', metavar='<METRIC>', required=self.create,
|
||||||
|
dest='meter_name', help='Metric to evaluate against')
|
||||||
|
parser.add_argument(
|
||||||
|
'--period', type=int, metavar='<PERIOD>', dest='period',
|
||||||
|
help='Length of each period (seconds) to evaluate over.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--evaluation-periods', type=int, metavar='<EVAL_PERIODS>',
|
||||||
|
dest='evaluation_periods',
|
||||||
|
help='Number of periods to evaluate over')
|
||||||
|
parser.add_argument(
|
||||||
|
'--statistic', metavar='<STATISTIC>', dest='statistic',
|
||||||
|
help='Statistic to evaluate, one of: ' + str(STATISTICS))
|
||||||
|
parser.add_argument(
|
||||||
|
'--comparison-operator', metavar='<OPERATOR>',
|
||||||
|
dest='comparison_operator',
|
||||||
|
help='Operator to compare with, one of: ' + str(ALARM_OPERATORS))
|
||||||
|
parser.add_argument(
|
||||||
|
'--threshold', type=float, metavar='<THRESHOLD>',
|
||||||
|
required=self.create, dest='threshold',
|
||||||
|
help='Threshold to evaluate against.')
|
||||||
|
parser.add_argument(
|
||||||
|
'-q', '--query', metavar='<QUERY>', dest='query',
|
||||||
|
help='key[op]data_type::value; list. data_type is optional, '
|
||||||
|
'but if supplied must be string, integer, float, or boolean.')
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
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'])
|
||||||
|
alarm['threshold_rule'] = utils.dict_from_parsed_args(
|
||||||
|
parsed_args, ['meter_name', 'period', 'evaluation_periods',
|
||||||
|
'statistic', 'comparison_operator', 'threshold',
|
||||||
|
'query'])
|
||||||
|
if self.create:
|
||||||
|
alarm['type'] = 'threshold'
|
||||||
|
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)
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from aodhclient import client
|
from aodhclient import client
|
||||||
|
from aodhclient.v2 import alarm
|
||||||
from aodhclient.v2 import capabilities
|
from aodhclient.v2 import capabilities
|
||||||
|
|
||||||
|
|
||||||
@@ -28,4 +29,5 @@ class Client(object):
|
|||||||
"""Initialize a new client for the Aodh v2 API."""
|
"""Initialize a new client for the Aodh v2 API."""
|
||||||
self.api = client.SessionClient(session, service_type=service_type,
|
self.api = client.SessionClient(session, service_type=service_type,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
self.alarm = alarm.AlarmManager(self)
|
||||||
self.capabilities = capabilities.CapabilitiesManager(self)
|
self.capabilities = capabilities.CapabilitiesManager(self)
|
||||||
|
|||||||
Reference in New Issue
Block a user