Add alarm definition create resource

Change-Id: I65e1c9f8697632e3c2004282bb624ed814828864
This commit is contained in:
Deklan Dieterly 2014-10-29 16:14:39 -06:00
parent 44eaa39649
commit ba39188b2f
28 changed files with 1075 additions and 257 deletions

View File

@ -39,6 +39,9 @@ events_driver = none
# The driver to use for the transforms repository
transforms_driver = mysql_transforms_repo
# The driver to use for the alarm definitions repository
alarm_definitions_driver = mysql_alarm_definitions_repo
# The driver to use for the notifications repository
notifications_driver = mysql_notifications_repo

View File

@ -0,0 +1,48 @@
# Copyright 2014 Hewlett-Packard
#
# 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 monasca.common import resource_api
from monasca.openstack.common import log
LOG = log.getLogger(__name__)
class AlarmDefinitionsV2API(object):
def __init__(self, global_conf):
LOG.debug('initializing AlarmDefinitionsV2API!')
self.global_conf = global_conf
@resource_api.Restify('/v2.0/alarm-definitions', method='post')
def do_post_alarm_definitions(self, req, res):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions/{id}', method='get')
def do_get_alarm_definition(self, req, res, id):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions/{id}', method='put')
def do_put_alarm_definitions(self, req, res, id):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions', method='get')
def do_get_alarm_definitions(self, req, res):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions/{id}', method='patch')
def do_patch_alarm_definitions(self, req, res, id):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions/{id}', method='delete')
def do_delete_alarm_definitions(self, req, res, id):
res.status = '501 Not Implemented'

View File

@ -46,30 +46,6 @@ class V2API(object):
def do_get_statistics(self, req, res):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions', method='post')
def do_post_alarm_definitions(self, req, res):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions/{id}', method='get')
def do_get_alarm_definition(self, req, res, id):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions/{id}', method='put')
def do_put_alarm_definitions(self, req, res, id):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions', method='get')
def do_get_alarm_definitions(self, req, res):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions/{id}', method='patch')
def do_patch_alarm_definitions(self, req, res, id):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions/{id}', method='delete')
def do_delete_alarm_definitions(self, req, res, id):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarms/{id}', method='put')
def do_put_alarms(self, req, res, id):
res.status = '501 Not Implemented'

View File

@ -4,7 +4,7 @@
# 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
# 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
@ -32,6 +32,7 @@ class TransformsV2API(object):
def do_get_transforms(self, req, res):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/events/transforms/{transform_id}', method='delete')
@resource_api.Restify('/v2.0/events/transforms/{transform_id}',
method='delete')
def do_delete_transforms(self, req, res, transform_id):
res.status = '501 Not Implemented'

View File

@ -6,7 +6,7 @@
# 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
# 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
@ -24,49 +24,56 @@ from paste.deploy import loadapp
from wsgiref import simple_server
METRICS_DISPATCHER_NAMESPACE = 'monasca.metrics_dispatcher'
ALARM_DEFINITIONS_DISPATCHER_NAMESPACE = 'monasca.alarm_definitions_dispatcher'
EVENTS_DISPATCHER_NAMESPACE = 'monasca.events_dispatcher'
TRANSFORMS_DISPATCHER_NAMESPACE = 'monasca.transforms_dispatcher'
NOTIFICATIONS_DISPATCHER_NAMESPACE = 'monasca.notifications_dispatcher'
LOG = log.getLogger(__name__)
global_opts = [
cfg.StrOpt('region', help='Region that API is running in')
]
global_opts = [cfg.StrOpt('region', help='Region that API is running in')]
cfg.CONF.register_opts(global_opts)
security_opts = [
cfg.ListOpt('default_authorized_roles', default=['admin'],
help='Roles that are allowed full access to the API'),
cfg.ListOpt('agent_authorized_roles', default=['agent'],
help='Roles that are only allowed to POST to the API'),
cfg.ListOpt('delegate_authorized_roles', default=['admin'],
help='Roles that are allowed to POST metrics on behalf of another tenant')
]
security_opts = [cfg.ListOpt('default_authorized_roles', default=['admin'],
help='Roles that are allowed full access to the '
'API'),
cfg.ListOpt('agent_authorized_roles', default=['agent'],
help='Roles that are only allowed to POST to '
'the API'),
cfg.ListOpt('delegate_authorized_roles', default=['admin'],
help='Roles that are allowed to POST metrics on '
'behalf of another tenant')]
security_group = cfg.OptGroup(name='security', title='security')
cfg.CONF.register_group(security_group)
cfg.CONF.register_opts(security_opts, security_group)
messaging_opts = [
cfg.StrOpt('driver', default='kafka', help='The message queue driver to use'),
cfg.StrOpt('metrics_message_format', default='reference',
help='The type of metrics message format to publish to the message queue'),
cfg.StrOpt('events_message_format', default='reference',
help='The type of events message format to publish to the message queue')
]
messaging_opts = [cfg.StrOpt('driver', default='kafka',
help='The message queue driver to use'),
cfg.StrOpt('metrics_message_format', default='reference',
help='The type of metrics message format to '
'publish to the message queue'),
cfg.StrOpt('events_message_format', default='reference',
help='The type of events message format to '
'publish to the message queue')]
messaging_group = cfg.OptGroup(name='messaging', title='messaging')
cfg.CONF.register_group(messaging_group)
cfg.CONF.register_opts(messaging_opts, messaging_group)
repositories_opts = [
cfg.StrOpt('metrics_driver', default='influxdb_metrics_repo', help='The repository driver to use for metrics'),
cfg.StrOpt('events_driver', default='fake_events_repo', help='The repository driver to use for events'),
cfg.StrOpt('transforms_driver', default='mysql_transforms_repo', help='The repository driver to use for transforms'),
cfg.StrOpt('notifications_driver', default='mysql_notifications_repo', help='The repository driver to use for notifications')
]
cfg.StrOpt('metrics_driver', default='influxdb_metrics_repo',
help='The repository driver to use for metrics'),
cfg.StrOpt('alarm_definitions_driver',
default='mysql_alarm_definitions_repo',
help='The repository driver to use for alarm definitions'),
cfg.StrOpt('events_driver', default='fake_events_repo',
help='The repository driver to use for events'),
cfg.StrOpt('transforms_driver', default='mysql_transforms_repo',
help='The repository driver to use for transforms'),
cfg.StrOpt('notifications_driver', default='mysql_notifications_repo',
help='The repository driver to use for notifications')]
repositories_group = cfg.OptGroup(name='repositories', title='repositories')
cfg.CONF.register_group(repositories_group)
@ -74,78 +81,58 @@ cfg.CONF.register_opts(repositories_opts, repositories_group)
dispatcher_opts = [
cfg.StrOpt('driver', default='monasca.v2.reference.metrics:Metrics',
help='The name of the dispatcher for the api server')
]
help='The name of the dispatcher for the api server')]
dispatcher_group = cfg.OptGroup(name='dispatcher', title='dispatcher')
cfg.CONF.register_group(dispatcher_group)
cfg.CONF.register_opts(dispatcher_opts, dispatcher_group)
kafka_opts = [
cfg.StrOpt('uri',
help='Address to kafka server. For example: '
'uri=192.168.1.191:9092'),
cfg.StrOpt('metrics_topic',
default='metrics',
help='The topic that metrics will be published too.'),
cfg.StrOpt('events_topic',
default='raw-events',
help='The topic that events will be published too.'),
cfg.StrOpt('group',
default='api',
help='The group name that this service belongs to.'),
cfg.IntOpt('wait_time',
default=1,
help='The wait time when no messages on kafka queue.'),
cfg.IntOpt('ack_time',
default=20,
help='The ack time back to kafka.'),
cfg.IntOpt('max_retry',
default=3,
help='The number of retry when there is a connection error.'),
cfg.BoolOpt('auto_commit',
default=False,
help='If automatically commmit when consume messages.'),
cfg.BoolOpt('async',
default=True,
help='The type of posting.'),
cfg.BoolOpt('compact',
default=True,
help=('Specify if the message received should be parsed.'
'If True, message will not be parsed, otherwise '
'messages will be parsed.')),
cfg.MultiOpt('partitions',
item_type=types.Integer(),
default=[0],
help='The sleep time when no messages on kafka queue.'),
cfg.BoolOpt('drop_data',
default=False,
help=('Specify if received data should be simply dropped. '
'This parameter is only for testing purposes.')),
]
kafka_opts = [cfg.StrOpt('uri', help='Address to kafka server. For example: '
'uri=192.168.1.191:9092'),
cfg.StrOpt('metrics_topic', default='metrics',
help='The topic that metrics will be published too.'),
cfg.StrOpt('events_topic', default='raw-events',
help='The topic that events will be published too.'),
cfg.StrOpt('group', default='api',
help='The group name that this service belongs to.'),
cfg.IntOpt('wait_time', default=1,
help='The wait time when no messages on kafka '
'queue.'),
cfg.IntOpt('ack_time', default=20,
help='The ack time back to kafka.'),
cfg.IntOpt('max_retry', default=3,
help='The number of retry when there is a '
'connection error.'),
cfg.BoolOpt('auto_commit', default=False,
help='If automatically commmit when consume '
'messages.'),
cfg.BoolOpt('async', default=True, help='The type of posting.'),
cfg.BoolOpt('compact', default=True, help=(
'Specify if the message received should be parsed.'
'If True, message will not be parsed, otherwise '
'messages will be parsed.')),
cfg.MultiOpt('partitions', item_type=types.Integer(),
default=[0],
help='The sleep time when no messages on kafka '
'queue.'),
cfg.BoolOpt('drop_data', default=False, help=(
'Specify if received data should be simply dropped. '
'This parameter is only for testing purposes.')), ]
kafka_group = cfg.OptGroup(name='kafka', title='title')
cfg.CONF.register_group(kafka_group)
cfg.CONF.register_opts(kafka_opts, kafka_group)
influxdb_opts = [
cfg.StrOpt('database_name'),
cfg.StrOpt('ip_address'),
cfg.StrOpt('port'),
cfg.StrOpt('user'),
cfg.StrOpt('password')
]
influxdb_opts = [cfg.StrOpt('database_name'), cfg.StrOpt('ip_address'),
cfg.StrOpt('port'), cfg.StrOpt('user'),
cfg.StrOpt('password')]
influxdb_group = cfg.OptGroup(name='influxdb', title='influxdb')
cfg.CONF.register_group(influxdb_group)
cfg.CONF.register_opts(influxdb_opts, influxdb_group)
mysql_opts = [
cfg.StrOpt('database_name'),
cfg.StrOpt('hostname'),
cfg.StrOpt('username'),
cfg.StrOpt('password')
]
mysql_opts = [cfg.StrOpt('database_name'), cfg.StrOpt('hostname'),
cfg.StrOpt('username'), cfg.StrOpt('password')]
mysql_group = cfg.OptGroup(name='mysql', title='mysql')
cfg.CONF.register_group(mysql_group)
@ -179,6 +166,11 @@ def api_app(conf):
app.add_resource('notifications', NOTIFICATIONS_DISPATCHER_NAMESPACE,
cfg.CONF.dispatcher.driver, [conf])
# load the alarm definitions resource
app.add_resource('alarm-definitions',
ALARM_DEFINITIONS_DISPATCHER_NAMESPACE,
cfg.CONF.dispatcher.driver, [conf])
return app

View File

@ -0,0 +1,25 @@
# Copyright 2014 Hewlett-Packard
#
# 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 abc
import six
@six.add_metaclass(abc.ABCMeta)
class AlarmDefinitionsRepository(object):
@abc.abstractmethod
def create_alarm_definition(self, tenant_id, name,
expression, sub_expr_list, description, severity, match_by, alarm_actions,
undetermined_actions, ok_action):
pass

View File

@ -0,0 +1,152 @@
# Copyright 2014 Hewlett-Packard
#
# 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 pyodbc
from oslo.config import cfg
from monasca.common.repositories import alarm_definitions_repository
from monasca.openstack.common import log
from monasca.openstack.common import uuidutils
from monasca.common.repositories import exceptions
LOG = log.getLogger(__name__)
class AlarmDefinitionsRepository(
alarm_definitions_repository.AlarmDefinitionsRepository):
database_driver = 'MySQL ODBC 5.3 Unicode Driver'
database_cnxn_template = 'DRIVER={' \
'%s};Server=%s;CHARSET=UTF8;Database=%s;Uid=%s' \
';Pwd=%s'
def __init__(self):
try:
self.conf = cfg.CONF
database_name = self.conf.mysql.database_name
database_server = self.conf.mysql.hostname
database_uid = self.conf.mysql.username
database_pwd = self.conf.mysql.password
self._cnxn_string = (
AlarmDefinitionsRepository.database_cnxn_template % (
AlarmDefinitionsRepository.database_driver,
database_server, database_name, database_uid,
database_pwd))
except Exception as ex:
LOG.exception(ex)
raise exceptions.RepositoryException(ex)
def create_alarm_definition(self, tenant_id, name, expression,
sub_expr_list, description, severity, match_by,
alarm_actions, undetermined_actions,
ok_actions):
try:
cnxn = pyodbc.connect(self._cnxn_string)
cursor = cnxn.cursor()
now = datetime.datetime.utcnow()
alarm_definition_id = uuidutils.generate_uuid()
cursor.execute("insert into alarm_definition("
"id, "
"tenant_id, "
"name, "
"description, "
"expression, "
"severity, "
"match_by,"
"actions_enabled, "
"created_at, "
"updated_at) "
"values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
alarm_definition_id, tenant_id, name.encode('utf8'),
description.encode('utf8'),
expression.encode('utf8'),
severity.upper().encode('utf8'),
",".join(match_by).encode('utf8'), 1, now, now)
for sub_expr in sub_expr_list:
sub_alarm_definition_id = uuidutils.generate_uuid()
sub_expr.id = sub_alarm_definition_id
cursor.execute("insert into sub_alarm_definition("
"id, "
"alarm_definition_id,"
"function, "
"metric_name, "
"operator, "
"threshold,"
"period, "
"periods, "
"created_at, "
"updated_at)"
" values(?,?,?,?,?,?,?,?,?,?)",
sub_alarm_definition_id, alarm_definition_id,
sub_expr.get_normalized_func().encode('utf8'),
sub_expr.get_normalized_metric_name().encode(
"utf8"),
sub_expr.get_normalized_operator().encode(
'utf8'),
sub_expr.get_threshold().encode('utf8'),
sub_expr.get_period().encode('utf8'),
sub_expr.get_periods().encode('utf8'), now, now)
for dimension in sub_expr.get_dimensions_as_list():
parsed_dimension = dimension.split('=')
cursor.execute(
"insert into sub_alarm_definition_dimension("
"sub_alarm_definition_id,"
"dimension_name,"
"value)"
"values(?,?,?)", sub_alarm_definition_id,
parsed_dimension[0].encode('utf8'),
parsed_dimension[1].encode('utf8'))
self._insert_into_alarm_action(cursor, alarm_definition_id,
alarm_actions, u"ALARM")
self._insert_into_alarm_action(cursor, alarm_definition_id,
undetermined_actions,
u"UNDETERMINED")
self._insert_into_alarm_action(cursor, alarm_definition_id,
ok_actions, u"OK")
cnxn.commit()
cnxn.close()
return alarm_definition_id
except Exception as ex:
LOG.exception(ex)
raise exceptions.RepositoryException(ex)
def _insert_into_alarm_action(self, cursor, alarm_definition_id, actions,
alarm_state):
for action in actions:
cursor.execute("select id from notification_method where id = ?",
action.encode('utf8'))
row = cursor.fetchone()
if not row:
raise exceptions.RepositoryException(
"Non-existent notification id {} submitted for {} "
"notification action".format(action.encode('utf8'),
alarm_state.encode('utf8')))
cursor.execute("insert into alarm_action("
"alarm_id,"
"alarm_state,"
"action_id)"
"values(?,?,?)", alarm_definition_id,
alarm_state.encode('utf8'), action.encode('utf8'))

View File

@ -25,21 +25,18 @@ RESOURCE_METHOD_FLAG = 'fab05a04-b861-4651-bd0c-9cb3eb9a6088'
LOG = log.getLogger(__name__)
def init_driver(namespace, driver_name, drv_invoke_args=None):
def init_driver(namespace, driver_name, drv_invoke_args=()):
"""Initialize the resource driver and returns it.
:param namespace: the resource namespace (in setup.cfg).
:param driver_name: the driver name (in monasca.conf)
:param invoke_args: args to pass to the driver (a tuple)
"""
invoke_args_tuple = ()
if drv_invoke_args:
invoke_args_tuple = drv_invoke_args
mgr = driver.DriverManager(
namespace = namespace,
name = driver_name,
invoke_on_load = True,
invoke_args = invoke_args_tuple
invoke_args = drv_invoke_args
)
return mgr.driver
@ -135,7 +132,7 @@ class ResourceAPI(falcon.API):
LOG.debug(self._routes)
def add_resource(self, resource_name, namespace, driver_name,
invoke_args=None, uri=None):
invoke_args=(), uri=None):
"""Loads the resource driver, and adds it to the routes.
:param resource_name: the name of the resource.

View File

@ -38,4 +38,4 @@ class KafkaDispatcher(monasca_api_v2.V2API):
msg = req.stream.read()
code = self._kafka_conn.send_messages(msg)
res.status = getattr(falcon, 'HTTP_' + str(code))
res.status = getattr(falcon, 'HTTP_' + str(code))

View File

View File

@ -0,0 +1,243 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2014 Hewlett-Packard
#
# 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 itertools
import sys
from pyparsing import CaselessLiteral
from pyparsing import alphanums
from pyparsing import delimitedList
from pyparsing import Forward
from pyparsing import Group
from pyparsing import Literal
from pyparsing import nums
from pyparsing import opAssoc
from pyparsing import operatorPrecedence
from pyparsing import Optional
from pyparsing import stringEnd
from pyparsing import Word
class SubExpr(object):
def __init__(self, tokens):
self.sub_expr = tokens
self.func = tokens.func
self.metric_name = tokens.metric_name
self.dimensions = tokens.dimensions.dimensions_list
self.operator = tokens.relational_op
self.threshold = tokens.threshold
self.period = tokens.period
self.periods = tokens.periods
def get_sub_expr_str(self):
return "".join(list(itertools.chain(*self.sub_expr)))
def get_fmtd_sub_expr(self):
result = "{}({}".format(self.func.encode('utf8'),
self.metric_name.encode('utf8'))
if self.dimensions:
result += "{{{}}}".format(self.dimensions.encode('utf8'))
if self.period:
result += ", {}".format(self.period.encode('utf8'))
result += ")"
result += " {} {}".format(self.operator.encode('utf8'),
self.threshold.encode('utf8'))
if self.periods:
result += " times {}".format(self.periods.encode('utf8'))
return result.decode('utf8')
def get_dimensions_str(self):
return self.dimensions
def get_operands_list(self):
return [self]
def get_func(self):
return self.func
def get_normalized_func(self):
return self.func.upper()
def get_metric_name(self):
return self.metric_name
def get_normalized_metric_name(self):
return self.metric_name.lower()
def get_dimensions(self):
return self.dimensions
def get_dimensions_as_list(self):
if self.dimensions:
return self.dimensions.split(",")
else:
return []
def get_operator(self):
return self.operator
def get_threshold(self):
return self.threshold
def get_period(self):
if self.period:
return self.period
else:
return u'60'
def get_periods(self):
if self.periods:
return self.periods
else:
return u'1'
def get_normalized_operator(self):
if self.operator.lower() == "lt" or self.operator == "<":
return u"LT"
elif self.operator.lower() == "gt" or self.operator == ">":
return u"GT"
elif self.operator.lower() == "lte" or self.operator == "<=":
return u"LTE"
elif self.operator.lower() == "gte" or self.operator == ">=":
return u"GTE"
class BinaryOp(object):
def __init__(self, tokens):
self.op = tokens[0][1]
self.operands = tokens[0][0::2]
def get_operands_list(self):
return ([sub_operand for operand in self.operands for sub_operand in
operand.get_operands_list()])
class AndSubExpr(BinaryOp):
""" Expand later as needed.
"""
pass
class OrSubExpr(BinaryOp):
"""Expand later as needed.
"""
pass
COMMA = Literal(",")
LPAREN = Literal("(")
RPAREN = Literal(")")
EQUAL = Literal("=")
LBRACE = Literal("{")
RBRACE = Literal("}")
# Initialize non-ascii unicode code points in the Basic Multilingual Plane.
unicode_printables = u''.join(
unichr(c) for c in xrange(128, 65536) if not unichr(c).isspace())
# Does not like comma. No Literals from above allowed.
valid_identifier_chars = (unicode_printables + alphanums + ".-_#!$%&'*+/:;?@["
"\\]^`|~")
metric_name = Word(valid_identifier_chars, min=1, max=255)("metric_name")
dimension_name = Word(valid_identifier_chars, min=1, max=255)
dimension_value = Word(valid_identifier_chars, min=1, max=255)
integer_number = Word(nums)
decimal_number = Word(nums + ".")
max = CaselessLiteral("max")
min = CaselessLiteral("min")
avg = CaselessLiteral("avg")
count = CaselessLiteral("count")
sum = CaselessLiteral("sum")
func = (max | min | avg | count | sum)("func")
less_than_op = (CaselessLiteral("<") | CaselessLiteral("lt"))
less_than_eq_op = (CaselessLiteral("<=") | CaselessLiteral("lte"))
greater_than_op = (CaselessLiteral(">") | CaselessLiteral("gt"))
greater_than_eq_op = (CaselessLiteral(">=") | CaselessLiteral("gte"))
# Order is important. Put longer prefix first.
relational_op = (
less_than_eq_op | less_than_op | greater_than_eq_op | greater_than_op)(
"relational_op")
AND = CaselessLiteral("and") | CaselessLiteral("&&")
OR = CaselessLiteral("or") | CaselessLiteral("||")
logical_op = (AND | OR)("logical_op")
times = CaselessLiteral("times")
dimension = Group(dimension_name + EQUAL + dimension_value)
dimension_list = Group(Optional(
LBRACE + delimitedList(dimension, delim=",", combine=True)(
"dimensions_list") + RBRACE))
metric = metric_name + dimension_list("dimensions")
period = integer_number("period")
threshold = decimal_number("threshold")
periods = integer_number("periods")
expression = Forward()
sub_expression = (func + LPAREN + metric + Optional(
COMMA + period) + RPAREN + relational_op + threshold + Optional(
times + periods) | LPAREN + expression + RPAREN)
sub_expression.setParseAction(SubExpr)
expression = operatorPrecedence(sub_expression,
[(AND, 2, opAssoc.LEFT, AndSubExpr),
(OR, 2, opAssoc.LEFT, OrSubExpr)])
class AlarmExprParser(object):
def __init__(self, expr):
self._expr = expr
def get_sub_expr_list(self):
parseResult = (expression + stringEnd).parseString(self._expr)
sub_expr_list = parseResult[0].get_operands_list()
return sub_expr_list
def main():
""" Used for development and testing.
:return:
"""
expr = "max(-_.千幸福的笑脸{घोड़ा=馬,dn2=dv2}, 60) gte 100 times 3 and " \
"(min(ເຮືອນ{dn3=dv3,家=дом}) < 10 or sum(biz{dn5=dv5}) > 99 and " \
"count(fizzle) lt 0 or count(baz) > 1)".decode('utf8')
# expr = "max(foo{hostname=mini-mon,千=千}, 120) > 100 and (max(bar)>100 \
# or max(biz)>100)".decode('utf8')
alarmExprParser = AlarmExprParser(expr)
r = alarmExprParser.get_sub_expr_list()
for sub_expression in r:
print sub_expression.get_sub_expr_str()
print sub_expression.get_fmtd_sub_expr()
print sub_expression.get_dimensions_str()
print
if __name__ == "__main__":
sys.exit(main())

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
class MockAuthFilter(object):
'''
This authorization filter doesn't do any authentication, it just copies the
@ -27,6 +28,7 @@ class MockAuthFilter(object):
env['HTTP_X_ROLES'] = 'admin'
return self.app(env, start_response)
def filter_factory(global_conf, **local_conf):
def validator_filter(app):
return MockAuthFilter(app, local_conf)

View File

@ -84,4 +84,4 @@ class BaseTestCase(testtools.TestCase):
os.write(fd, contents)
finally:
os.close(fd)
return tempfiles
return tempfiles

View File

@ -0,0 +1,46 @@
# Copyright 2014 Hewlett-Packard
#
# 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 voluptuous import Schema, Length, Optional
from voluptuous import Required, Any, All
from monasca.openstack.common import log
from monasca.v2.common.schemas import exceptions
LOG = log.getLogger(__name__)
alarm_definition_schema = {
Required('name'): All(Any(str, unicode), Length(max=250)),
Required('expression'): All(Any(str, unicode), Length(max=4096)),
Optional('description'): All(Any(str, unicode), Length(max=250)),
Optional('severity'): All(
Any('low', 'medium', 'high', 'critical', 'LOW', "MEDIUM", 'HIGH',
'CRITICAL')),
Optional('match_by'): All(Any([unicode], [str]), Length(max=255)),
Optional('ok_actions'): All(Any([str], [unicode]), Length(max=400)),
Optional('alarm_actions'): All(Any([str], [unicode]), Length(max=400)),
Optional('undetermined_actions'): All(Any([str], [unicode]),
Length(max=400))}
request_body_schema = Schema(alarm_definition_schema, required=True,
extra=True)
def validate(msg):
try:
request_body_schema(msg)
except Exception as ex:
LOG.debug(ex)
raise exceptions.ValidationException(str(ex))

View File

@ -19,8 +19,8 @@ from monasca.v2.common.schemas import exceptions
LOG = log.getLogger(__name__)
# TODO: Add regex to validate dimension names don't use any excluded characters.
dimensions_schema = Schema({All(Any(str, unicode), Length(max=255)): All(Any(str, unicode), Length(max=255))})
dimensions_schema = Schema({All(Any(str, unicode), Length(max=255)):
All(Any(str, unicode), Length(max=255))})
def validate(dimensions):
@ -28,4 +28,4 @@ def validate(dimensions):
dimensions_schema(dimensions)
except Exception as ex:
LOG.debug(ex)
raise exceptions.ValidationException(str(ex))
raise exceptions.ValidationException(str(ex))

View File

@ -20,7 +20,9 @@ from monasca.v2.common.schemas import exceptions
LOG = log.getLogger(__name__)
# TODO: Add regex to validate key/values don't use any excluded characters.
event_schema_request_body = Schema({All(Any(str, unicode), Length(max=255)): All(Any(None, str, unicode, bool, int, float, dict, []))})
event_schema_request_body = Schema({All(Any(str, unicode), Length(max=255)):
All(Any(None, str, unicode, bool, int,
float, dict, []))})
def validate(body):
@ -28,4 +30,4 @@ def validate(body):
event_schema_request_body(body)
except Exception as ex:
LOG.debug(ex)
raise exceptions.ValidationException(str(ex))
raise exceptions.ValidationException(str(ex))

View File

@ -12,5 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
class ValidationException(Exception):
pass
pass

View File

@ -27,4 +27,4 @@ def validate(name):
metric_name_schema(name)
except Exception as ex:
LOG.debug(ex)
raise exceptions.ValidationException(str(ex))
raise exceptions.ValidationException(str(ex))

View File

@ -36,4 +36,4 @@ def validate(msg):
request_body_schema(msg)
except Exception as ex:
LOG.debug(ex)
raise exceptions.ValidationException(str(ex))
raise exceptions.ValidationException(str(ex))

View File

@ -22,7 +22,8 @@ LOG = log.getLogger(__name__)
transform_schema = {
Required('name'): Schema(All(Any(str, unicode), Length(max=64))),
Required('description'): Schema(All(Any(str, unicode), Length(max=250))),
Required('specification'): Schema(All(Any(str, unicode), Length(max=64536))),
Required('specification'):
Schema(All(Any(str, unicode), Length(max=64536))),
Optional('enabled'): bool
}
@ -34,4 +35,4 @@ def validate(msg):
request_body_schema(msg)
except Exception as ex:
LOG.debug(ex)
raise exceptions.ValidationException(str(ex))
raise exceptions.ValidationException(str(ex))

View File

@ -12,5 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
def date_handler(obj):
return obj.isoformat() if hasattr(obj, 'isoformat') else obj
return obj.isoformat() if hasattr(obj, 'isoformat') else obj

View File

@ -0,0 +1,289 @@
# Copyright 2014 Hewlett-Packard
#
# 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 json
from pyparsing import ParseException
import falcon
from oslo.config import cfg
from monasca.common.repositories import exceptions
from monasca.common import resource_api
from monasca.api.alarm_definitions_api_v2 import AlarmDefinitionsV2API
from monasca.expression_parser.alarm_expr_parser import AlarmExprParser
from monasca.openstack.common import log
from monasca.v2.reference import helpers
from monasca.v2.common.schemas import \
alarm_definition_request_body_schema as schema_alarms
from monasca.v2.common.schemas import exceptions as schemas_exceptions
from monasca.v2.reference.helpers import read_json_msg_body
from monasca.common.messaging import exceptions as message_queue_exceptions
LOG = log.getLogger(__name__)
class AlarmDefinitions(AlarmDefinitionsV2API):
def __init__(self, global_conf):
try:
super(AlarmDefinitions, self).__init__(global_conf)
self._region = cfg.CONF.region
self._default_authorized_roles = \
cfg.CONF.security.default_authorized_roles
self._delegate_authorized_roles = \
cfg.CONF.security.delegate_authorized_roles
self._post_metrics_authorized_roles = \
cfg.CONF.security.default_authorized_roles + \
cfg.CONF.security.agent_authorized_roles
self._message_queue = \
resource_api.init_driver('monasca.messaging',
cfg.CONF.messaging.driver,
(['events']))
self._alarm_definitions_repo = resource_api.init_driver(
'monasca.repositories',
cfg.CONF.repositories.alarm_definitions_driver)
except Exception as ex:
LOG.exception(ex)
raise exceptions.RepositoryException(ex)
@resource_api.Restify('/v2.0/alarm-definitions', method='post')
def do_post_alarm_definitions(self, req, res):
helpers.validate_authorization(req, self._default_authorized_roles)
alarm_definition = read_json_msg_body(req)
self._validate_alarm_definition(alarm_definition)
tenant_id = helpers.get_tenant_id(req)
name = get_query_alarm_definition_name(alarm_definition)
expression = get_query_alarm_definition_expression(
alarm_definition)
description = get_query_alarm_definition_description(
alarm_definition)
severity = get_query_alarm_definition_severity(
alarm_definition)
match_by = get_query_alarm_definition_match_by(
alarm_definition)
alarm_actions = get_query_alarm_definition_alarm_actions(
alarm_definition)
undetermined_actions = \
get_query_alarm_definition_undetermined_actions(
alarm_definition)
ok_actions = get_query_ok_actions(alarm_definition)
result = self._alarm_definition_create(tenant_id, name, expression,
description, severity, match_by,
alarm_actions,
undetermined_actions,
ok_actions)
helpers.add_links_to_resource(result, req.uri)
res.body = json.dumps(result, ensure_ascii=False).encode('utf8')
res.status = falcon.HTTP_201
@resource_api.Restify('/v2.0/alarm-definitions/{id}', method='get')
def do_get_alarm_definition(self, req, res, id):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions/{id}', method='put')
def do_put_alarm_definitions(self, req, res, id):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions', method='get')
def do_get_alarm_definitions(self, req, res):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions/{id}', method='patch')
def do_patch_alarm_definitions(self, req, res, id):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarm-definitions/{id}', method='delete')
def do_delete_alarm_definitions(self, req, res, id):
res.status = '501 Not Implemented'
def _validate_alarm_definition(self, alarm_definition):
try:
schema_alarms.validate(alarm_definition)
except schemas_exceptions.ValidationException as ex:
LOG.debug(ex)
raise falcon.HTTPBadRequest('Bad reqeust', ex.message)
def _alarm_definition_create(self, tenant_id, name, expression,
description, severity, match_by,
alarm_actions, undetermined_actions,
ok_actions):
try:
sub_expr_list = AlarmExprParser(expression).get_sub_expr_list()
alarm_definition_id = \
self._alarm_definitions_repo.create_alarm_definition(
tenant_id, name, expression, sub_expr_list, description,
severity, match_by, alarm_actions, undetermined_actions,
ok_actions)
self._send_alarm_definition_created_event(tenant_id,
alarm_definition_id,
name, expression,
sub_expr_list,
description, match_by)
result = (
{u'alarm_actions': alarm_actions, u'ok_actions': ok_actions,
u'description': description, u'match_by': match_by,
u'severity': severity.lower(), u'actions_enabled': u'true',
u'undetermined_actions': undetermined_actions,
u'expression': expression, u'id': alarm_definition_id,
u'name': name})
return result
except ParseException as ex:
LOG.exception(ex)
title = "Invalid alarm expression".encode('utf8')
msg = "parser failed on expression '{}' at column {}".format(
expression.encode('utf8'), str(ex.column).encode('utf'))
raise falcon.HTTPBadRequest(title, msg)
except exceptions.RepositoryException as ex:
LOG.exception(ex)
raise falcon.HTTPInternalServerError('Service unavailable',
ex.message[1])
def _send_alarm_definition_created_event(self, tenant_id,
alarm_definition_id, name,
expression, sub_expr_list,
description, match_by):
alarm_definition_created_event_msg = {
u'alarm-definition-created': {u'tenantId': tenant_id,
u'alarmDefinitionId':
alarm_definition_id,
u'alarmName': name,
u'alarmDescription': description,
u'alarmExpression': expression,
u'matchBy': match_by}}
sub_expr_event_msg = {}
for sub_expr in sub_expr_list:
sub_expr_event_msg[sub_expr.id] = {
u'function': sub_expr.get_normalized_func()}
metric_definition = {
u'name': sub_expr.get_normalized_metric_name()}
sub_expr_event_msg[sub_expr.id][
u'metricDefinition'] = metric_definition
dimensions = {}
for dimension in sub_expr.get_dimensions_as_list():
parsed_dimension = dimension.split("=")
dimensions[parsed_dimension[0]] = parsed_dimension[1]
metric_definition[u'dimensions'] = dimensions
sub_expr_event_msg[sub_expr.id][
u'operator'] = sub_expr.get_normalized_operator()
sub_expr_event_msg[sub_expr.id][
u'threshold'] = sub_expr.get_threshold()
sub_expr_event_msg[sub_expr.id][u'period'] = sub_expr.get_period()
sub_expr_event_msg[sub_expr.id][
u'periods'] = sub_expr.get_periods()
sub_expr_event_msg[sub_expr.id][
u'expression'] = sub_expr.get_fmtd_sub_expr()
alarm_definition_created_event_msg[u'alarm-definition-created'][
u'alarmSubExpressions'] = sub_expr_event_msg
self._send_event(alarm_definition_created_event_msg)
def _send_event(self, event_msg):
try:
self._message_queue.send_message(
json.dumps(event_msg, ensure_ascii=False).encode('utf8'))
except message_queue_exceptions.MessageQueueException as ex:
LOG.exception(ex)
raise falcon.HTTPInternalServerError(
'Message queue service unavailable'.encode('utf8'),
ex.message.encode('utf8'))
def get_query_alarm_definition_name(alarm_definition):
try:
if 'name' in alarm_definition:
name = alarm_definition['name']
return name
else:
raise Exception("Missing name")
except Exception as ex:
LOG.debug(ex)
raise falcon.HTTPBadRequest('Bad request', ex.message)
def get_query_alarm_definition_expression(alarm_definition):
try:
if 'expression' in alarm_definition:
expression = alarm_definition['expression']
return expression
else:
raise Exception("Missing expression")
except Exception as ex:
LOG.debug(ex)
raise falcon.HTTPBadRequest('Bad request', ex.message)
def get_query_alarm_definition_description(alarm_definition):
if 'description' in alarm_definition:
return alarm_definition['description']
else:
return ''
def get_query_alarm_definition_severity(alarm_definition):
if 'severity' in alarm_definition:
severity = alarm_definition['severity']
severity = severity.decode('utf8').lower()
if severity not in ['low', 'medium', 'high', 'critical']:
raise falcon.HTTPBadRequest('Bad request, Invalid severity')
return severity
else:
return ''
def get_query_alarm_definition_match_by(alarm_definition):
if 'match_by' in alarm_definition:
match_by = alarm_definition['match_by']
return match_by
else:
return []
def get_query_alarm_definition_alarm_actions(alarm_definition):
if 'alarm_actions' in alarm_definition:
alarm_actions = alarm_definition['alarm_actions']
return alarm_actions
else:
return []
def get_query_alarm_definition_undetermined_actions(alarm_definition):
if 'undetermined_actions' in alarm_definition:
undetermined_actions = alarm_definition['undetermined_actions']
return undetermined_actions
else:
return []
def get_query_ok_actions(alarm_definition):
if 'ok_actions' in alarm_definition:
ok_actions = alarm_definition['ok_actions']
return ok_actions
else:
return []

View File

@ -4,7 +4,7 @@
# 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
# 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
@ -24,7 +24,8 @@ from monasca.common.messaging import exceptions as message_queue_exceptions
from monasca.common.messaging.message_formats import events_transform_factory
from monasca.v2.common import utils
from monasca.v2.common.schemas import exceptions as schemas_exceptions
from monasca.v2.common.schemas import events_request_body_schema as schemas_event
from monasca.v2.common.schemas import \
events_request_body_schema as schemas_event
from monasca.v2.reference import helpers
from stevedore import driver
@ -36,13 +37,19 @@ class Events(monasca_events_api_v2.EventsV2API):
def __init__(self, global_conf):
super(Events, self).__init__(global_conf)
self._region = cfg.CONF.region
self._default_authorized_roles = cfg.CONF.security.default_authorized_roles
self._delegate_authorized_roles = cfg.CONF.security.delegate_authorized_roles
self._post_events_authorized_roles = cfg.CONF.security.default_authorized_roles + \
cfg.CONF.security.agent_authorized_roles
self._event_transform = events_transform_factory.create_events_transform()
self._message_queue = resource_api.init_driver('monasca.messaging',
cfg.CONF.messaging.driver, ['raw-events'])
self._default_authorized_roles = \
cfg.CONF.security.default_authorized_roles
self._delegate_authorized_roles = \
cfg.CONF.security.delegate_authorized_roles
self._post_events_authorized_roles = \
cfg.CONF.security.default_authorized_roles + \
cfg.CONF.security.agent_authorized_roles
self._event_transform = \
events_transform_factory.create_events_transform()
self._message_queue = \
resource_api.init_driver('monasca.messaging',
cfg.CONF.messaging.driver,
['raw-events'])
def _validate_event(self, event):
"""Validates the event
@ -63,11 +70,13 @@ class Events(monasca_events_api_v2.EventsV2API):
:raises: falcon.HTTPServiceUnavailable
"""
try:
str_msg = json.dumps(event, default=utils.date_handler)
str_msg = json.dumps(event, default=utils.date_handler,
ensure_ascii=False).encode('utf8')
self._message_queue.send_message(str_msg)
except message_queue_exceptions.MessageQueueException as ex:
LOG.exception(ex)
raise falcon.HTTPInternalServerError('Service unavailable', ex.message)
raise falcon.HTTPInternalServerError('Service unavailable',
ex.message)
@resource_api.Restify('/v2.0/events/', method='post')
def do_post_events(self, req, res):
@ -76,6 +85,7 @@ class Events(monasca_events_api_v2.EventsV2API):
event = helpers.read_http_resource(req)
self._validate_event(event)
tenant_id = helpers.get_tenant_id(req)
transformed_event = self._event_transform(event, tenant_id, self._region)
transformed_event = self._event_transform(event, tenant_id,
self._region)
self._send_event(transformed_event)
res.status = falcon.HTTP_204
res.status = falcon.HTTP_204

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import json
import falcon
from falcon.util.uri import parse_query_string
@ -25,6 +26,23 @@ import simplejson
LOG = log.getLogger(__name__)
def read_json_msg_body(req):
'''
Read the json_msg from the http request body and return them as JSON.
:param req: HTTP request object.
:return: Returns the metrics as a JSON object.
:raises falcon.HTTPBadRequest:
'''
try:
msg = req.stream.read()
json_msg = json.loads(msg)
return json_msg
except ValueError as ex:
LOG.debug(ex)
raise falcon.HTTPBadRequest('Bad request',
'Request body is not valid JSON')
def validate_json_content_type(req):
if req.content_type not in ['application/json']:
raise falcon.HTTPBadRequest('Bad request', 'Bad content type. Must be '
@ -79,7 +97,7 @@ def get_tenant_id(req):
return req.get_header('X-TENANT-ID')
def get_cross_tenant_or_tenant_id(req, delegate_authorized_roles):
def get_x_tenant_or_tenant_id(req, delegate_authorized_roles):
"""Evaluates whether the tenant ID or cross tenant ID should be returned.
:param req: HTTP request object.
@ -95,16 +113,24 @@ def get_cross_tenant_or_tenant_id(req, delegate_authorized_roles):
return get_tenant_id(req)
def get_query_name(req):
"""Returns the query param "name" if supplied.
def get_query_name(req, name_required=False):
'''
Returns the query param "name" if supplied.
:param req: HTTP request object.
"""
params = parse_query_string(req.query_string)
name = ''
if 'name' in params:
name = params['name']
return name
'''
try:
params = parse_query_string(req.query_string)
if 'name' in params:
name = params['name']
return name
else:
if name_required:
raise Exception("Missing name")
else:
return ''
except Exception as ex:
LOG.debug(ex)
raise falcon.HTTPBadRequest('Bad request', ex.message)
def get_query_dimensions(req):

View File

@ -27,7 +27,11 @@ from monasca.v2.common import utils
from monasca.v2.common.schemas import exceptions as schemas_exceptions
from monasca.v2.common.schemas import \
metrics_request_body_schema as schemas_metrics
from monasca.common.repositories import exceptions
from monasca.v2.reference import helpers
from monasca.v2.reference.helpers import read_json_msg_body
LOG = log.getLogger(__name__)
@ -35,21 +39,30 @@ LOG = log.getLogger(__name__)
class Metrics(monasca_api_v2.V2API):
def __init__(self, global_conf):
super(Metrics, self).__init__(global_conf)
self._region = cfg.CONF.region
self._default_authorized_roles = \
cfg.CONF.security.default_authorized_roles
self._delegate_authorized_roles = \
cfg.CONF.security.delegate_authorized_roles
self._post_metrics_authorized_roles = \
cfg.CONF.security.default_authorized_roles + \
cfg.CONF.security.agent_authorized_roles
self._metrics_transform = \
metrics_transform_factory.create_metrics_transform()
self._message_queue = resource_api.init_driver('monasca.messaging',
cfg.CONF.messaging.driver, ['metrics'])
self._metrics_repo = resource_api.init_driver('monasca.repositories',
cfg.CONF.repositories.metrics_driver)
try:
super(Metrics, self).__init__(global_conf)
self._region = cfg.CONF.region
self._default_authorized_roles = \
cfg.CONF.security.default_authorized_roles
self._delegate_authorized_roles = \
cfg.CONF.security.delegate_authorized_roles
self._post_metrics_authorized_roles = \
cfg.CONF.security.default_authorized_roles + \
cfg.CONF.security.agent_authorized_roles
self._metrics_transform = \
metrics_transform_factory.create_metrics_transform()
self._message_queue = resource_api.init_driver(
'monasca.messaging',
cfg.CONF.messaging.driver,
['metrics'])
self._metrics_repo = resource_api.init_driver(
'monasca.repositories', cfg.CONF.repositories.metrics_driver)
except Exception as ex:
LOG.exception(ex)
raise falcon.HTTPInternalServerError('Service unavailable',
ex.message)
def _validate_metrics(self, metrics):
"""Validates the metrics
@ -101,8 +114,8 @@ class Metrics(monasca_api_v2.V2API):
raise falcon.HTTPServiceUnavailable('Service unavailable',
ex.message)
def _measurement_list(self, tenant_id, name, dimensions,
start_timestamp, end_timestamp):
def _measurement_list(self, tenant_id, name, dimensions, start_timestamp,
end_timestamp):
try:
return self._metrics_repo.measurement_list(tenant_id, name,
dimensions,
@ -113,21 +126,19 @@ class Metrics(monasca_api_v2.V2API):
raise falcon.HTTPServiceUnavailable('Service unavailable',
ex.message)
def _metric_statistics(self, tenant_id, name, dimensions,
start_timestamp, end_timestamp, statistics, period):
def _metric_statistics(self, tenant_id, name, dimensions, start_timestamp,
end_timestamp, statistics, period):
try:
return self._metrics_repo.metrics_statistics(tenant_id, name,
dimensions,
start_timestamp,
end_timestamp,
statistics,
period)
dimensions,
start_timestamp,
end_timestamp,
statistics, period)
except Exception as ex:
LOG.exception(ex)
raise falcon.HTTPServiceUnavailable('Service unavailable',
ex.message)
@resource_api.Restify('/v2.0/metrics/', method='post')
def do_post_metrics(self, req, res):
helpers.validate_json_content_type(req)
@ -135,8 +146,9 @@ class Metrics(monasca_api_v2.V2API):
self._post_metrics_authorized_roles)
metrics = helpers.read_http_resource(req)
self._validate_metrics(metrics)
tenant_id = helpers.get_cross_tenant_or_tenant_id(req,
self._delegate_authorized_roles)
tenant_id = \
helpers.get_x_tenant_or_tenant_id(req,
self._delegate_authorized_roles)
transformed_metrics = self._metrics_transform(metrics, tenant_id,
self._region)
self._send_metrics(transformed_metrics)
@ -182,7 +194,7 @@ class Metrics(monasca_api_v2.V2API):
statistics = helpers.get_query_statistics(req)
period = helpers.get_query_period(req)
result = self._metric_statistics(tenant_id, name, dimensions,
start_timestamp, end_timestamp,
statistics, period)
start_timestamp, end_timestamp,
statistics, period)
res.body = json.dumps(result, ensure_ascii=False).encode('utf8')
res.status = falcon.HTTP_200
res.status = falcon.HTTP_200

View File

@ -4,7 +4,7 @@
# 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
# 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
@ -26,20 +26,21 @@ from monasca.api import monasca_notifications_api_v2
from monasca.common import resource_api
from monasca.common.repositories import exceptions as repository_exceptions
from monasca.v2.common.schemas import exceptions as schemas_exceptions
from monasca.v2.common.schemas import notifications_request_body_schema as schemas_notifications
from monasca.v2.common.schemas import \
notifications_request_body_schema as schemas_notifications
from monasca.v2.reference import helpers
LOG = log.getLogger(__name__)
class Notifications(monasca_notifications_api_v2.NotificationsV2API):
def __init__(self, global_conf):
super(Notifications, self).__init__(global_conf)
self._region = cfg.CONF.region
self._default_authorized_roles = cfg.CONF.security.default_authorized_roles
self._notifications_repo = resource_api.init_driver('monasca.repositories',
cfg.CONF.repositories.notifications_driver)
self._default_authorized_roles = \
cfg.CONF.security.default_authorized_roles
self._notifications_repo = resource_api.init_driver(
'monasca.repositories', cfg.CONF.repositories.notifications_driver)
def _validate_notification(self, notification):
"""Validates the notification
@ -64,20 +65,16 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API):
notification_type = notification['type'].upper()
address = notification['address']
if self._notifications_repo.exists(tenant_id, name):
raise falcon.HTTPConflict(
'Conflict', ('Notification Method already exists: tenant_id=%s name=%s' %
(tenant_id, name)), code=409)
self._notifications_repo.create_notification(
id,
tenant_id,
name,
notification_type,
address)
raise falcon.HTTPConflict('Conflict', (
'Notification Method already exists: tenant_id=%s name=%s' % (
tenant_id, name)), code=409)
self._notifications_repo.create_notification(id, tenant_id, name,
notification_type,
address)
except repository_exceptions.RepositoryException as ex:
LOG.error(ex)
raise falcon.HTTPInternalServerError(
'Service unavailable',
ex.message)
raise falcon.HTTPInternalServerError('Service unavailable',
ex.message)
def _update_notification(self, id, tenant_id, notification):
"""Update the notification using the repository.
@ -89,31 +86,24 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API):
name = notification['name']
notification_type = notification['type'].upper()
address = notification['address']
self._notifications_repo.update_notification(
id,
tenant_id,
name,
notification_type,
address)
self._notifications_repo.update_notification(id, tenant_id, name,
notification_type,
address)
except repository_exceptions.DoesNotExistException:
helpers.raise_not_found_exception('notification', id, tenant_id)
except repository_exceptions.RepositoryException as ex:
LOG.error(ex)
raise falcon.HTTPInternalServerError(
'Service unavailable',
ex.message)
raise falcon.HTTPInternalServerError('Service unavailable',
ex.message)
def _create_notification_response(self, id, notification, uri):
name = notification['name']
notification_type = notification['type'].upper()
address = notification['address']
response = {
'id': id,
'name': name,
'type': notification_type,
'address': address
}
return json.dumps(helpers.add_links_to_resource(response, uri))
response = {'id': id, 'name': name, 'type': notification_type,
'address': address}
return json.dumps(helpers.add_links_to_resource(response, uri),
ensure_ascii=False).encode('utf8')
def _list_notifications(self, tenant_id, uri):
"""Lists all notifications for this tenant id.
@ -128,9 +118,8 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API):
helpers.add_links_to_resource_list(notifications, uri))
except repository_exceptions.RepositoryException as ex:
LOG.error(ex)
raise falcon.HTTPInternalServerError(
'Service unavailable',
ex.message)
raise falcon.HTTPInternalServerError('Service unavailable',
ex.message)
def _list_notification(self, tenant_id, notification_id, uri):
"""Lists the notification by id.
@ -141,17 +130,16 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API):
"""
try:
notifications = self._notifications_repo.list_notification(
tenant_id,
notification_id)
tenant_id, notification_id)
return json.dumps(
helpers.add_links_to_resource(notifications, uri))
except repository_exceptions.DoesNotExistException:
helpers.raise_not_found_exception('notification', notification_id, tenant_id)
helpers.raise_not_found_exception('notification', notification_id,
tenant_id)
except repository_exceptions.RepositoryException as ex:
LOG.error(ex)
raise falcon.HTTPInternalServerError(
'Service unavailable',
ex.message)
raise falcon.HTTPInternalServerError('Service unavailable',
ex.message)
def _delete_notification(self, tenant_id, notification_id):
"""Deletes the notification using the repository.
@ -161,16 +149,15 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API):
:raises: falcon.HTTPServiceUnavailable,falcon.HTTPError (404)
"""
try:
self._notifications_repo.delete_notification(
tenant_id,
notification_id)
self._notifications_repo.delete_notification(tenant_id,
notification_id)
except repository_exceptions.DoesNotExistException:
helpers.raise_not_found_exception('notification', notification_id, tenant_id)
helpers.raise_not_found_exception('notification', notification_id,
tenant_id)
except repository_exceptions.RepositoryException as ex:
LOG.error(ex)
raise falcon.HTTPInternalServerError(
'Service unavailable',
ex.message)
raise falcon.HTTPInternalServerError('Service unavailable',
ex.message)
@resource_api.Restify('/v2.0/notification-methods', method='post')
def do_post_notification_methods(self, req, res):
@ -181,10 +168,8 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API):
id = uuidutils.generate_uuid()
tenant_id = helpers.get_tenant_id(req)
self._create_notification(id, tenant_id, notification)
res.body = self._create_notification_response(
id,
notification,
req.uri)
res.body = self._create_notification_response(id, notification,
req.uri)
res.status = falcon.HTTP_200
@resource_api.Restify('/v2.0/notification-methods', method='get')
@ -216,8 +201,6 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API):
self._validate_notification(notification)
tenant_id = helpers.get_tenant_id(req)
self._update_notification(id, tenant_id, notification)
res.body = self._create_notification_response(
id,
notification,
req.uri)
res.body = self._create_notification_response(id, notification,
req.uri)
res.status = falcon.HTTP_200

View File

@ -4,7 +4,7 @@
# 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
# 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
@ -12,7 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
# TODO: Used simplejson to read the yaml as simplejson transforms to "str" not "unicode"
# TODO: Used simplejson to read the yaml as simplejson transforms to "str"
# not "unicode"
import json
import simplejson
@ -26,7 +27,8 @@ from monasca.api import monasca_transforms_api_v2
from monasca.common import resource_api
from monasca.common.repositories import exceptions as repository_exceptions
from monasca.v2.common.schemas import exceptions as schemas_exceptions
from monasca.v2.common.schemas import transforms_request_body_schema as schemas_transforms
from monasca.v2.common.schemas import \
transforms_request_body_schema as schemas_transforms
from monasca.v2.reference import helpers
from stevedore import driver
@ -38,9 +40,10 @@ class Transforms(monasca_transforms_api_v2.TransformsV2API):
def __init__(self, global_conf):
super(Transforms, self).__init__(global_conf)
self._region = cfg.CONF.region
self._default_authorized_roles = cfg.CONF.security.default_authorized_roles
self._transforms_repo = resource_api.init_driver('monasca.repositories',
cfg.CONF.repositories.transforms_driver)
self._default_authorized_roles = \
cfg.CONF.security.default_authorized_roles
self._transforms_repo = resource_api.init_driver(
'monasca.repositories', cfg.CONF.repositories.transforms_driver)
def _validate_transform(self, transform):
"""Validates the transform
@ -65,32 +68,31 @@ class Transforms(monasca_transforms_api_v2.TransformsV2API):
description = transform['description']
specification = transform['specification']
enabled = transform['enabled']
self._transforms_repo.create_transforms(id, tenant_id, name, description, specification, enabled)
self._transforms_repo.create_transforms(id, tenant_id, name,
description, specification,
enabled)
except repository_exceptions.RepositoryException as ex:
LOG.error(ex)
raise falcon.HTTPInternalServerError('Service unavailable', ex.message)
raise falcon.HTTPInternalServerError('Service unavailable',
ex.message)
def _create_transform_response(self, id, transform):
name = transform['name']
description = transform['description']
specification = transform['specification']
enabled = transform['enabled']
response = {
'id': id,
'name': name,
'description': description,
'specification': specification,
'enabled': enabled
}
response = {'id': id, 'name': name, 'description': description,
'specification': specification, 'enabled': enabled}
return json.dumps(response)
def _list_transforms(self, tenant_id):
try:
transforms = self._transforms_repo.list_transforms(tenant_id)
transforms = self._transforms_repo.list_transforms(tenant_id)
return json.dumps(transforms)
except repository_exceptions.RepositoryException as ex:
LOG.error(ex)
raise falcon.HTTPInternalServerError('Service unavailable', ex.message)
raise falcon.HTTPInternalServerError('Service unavailable',
ex.message)
def _delete_transform(self, tenant_id, transform_id):
try:
@ -99,7 +101,8 @@ class Transforms(monasca_transforms_api_v2.TransformsV2API):
raise falcon.HTTPNotFound()
except repository_exceptions.RepositoryException as ex:
LOG.error(ex)
raise falcon.HTTPInternalServerError('Service unavailable', ex.message)
raise falcon.HTTPInternalServerError('Service unavailable',
ex.message)
@resource_api.Restify('/v2.0/events/transforms', method='post')
def do_post_transforms(self, req, res):
@ -120,9 +123,10 @@ class Transforms(monasca_transforms_api_v2.TransformsV2API):
res.body = self._list_transforms(tenant_id)
res.status = falcon.HTTP_200
@resource_api.Restify('/v2.0/events/transforms/{transform_id}', method='delete')
@resource_api.Restify('/v2.0/events/transforms/{transform_id}',
method='delete')
def do_delete_transforms(self, req, res, transform_id):
helpers.validate_authorization(req, self._default_authorized_roles)
tenant_id = helpers.get_tenant_id(req)
self._delete_transform(tenant_id, transform_id)
res.status = falcon.HTTP_204
res.status = falcon.HTTP_204

View File

@ -37,6 +37,9 @@ monasca.metrics_dispatcher =
kafka = monasca.dispatcher.kafka_dispatcher:KafkaDispatcher
v2_reference = monasca.v2.reference.metrics:Metrics
monasca.alarm_definitions_dispatcher =
v2_reference = monasca.v2.reference.alarm_definitions:AlarmDefinitions
monasca.events_dispatcher =
v2_reference = monasca.v2.reference.events:Events
@ -62,6 +65,7 @@ monasca.repositories =
influxdb_metrics_repo = monasca.common.repositories.influxdb.metrics_repository:MetricsRepository
fake_events_repo = monasca.common.repositories.fake.events_repository:EventsRepository
mysql_transforms_repo = monasca.common.repositories.mysql.transforms_repository:TransformsRepository
mysql_alarm_definitions_repo = monasca.common.repositories.mysql.alarm_definitions_repository:AlarmDefinitionsRepository
mysql_notifications_repo = monasca.common.repositories.mysql.notifications_repository:NotificationsRepository
[pbr]