Add alarm history resource

Change-Id: Ic19c09cc0808c994cadfb7e3c7680dd58438852d
This commit is contained in:
Deklan Dieterly 2014-11-13 12:57:00 -07:00
parent 000056f174
commit 71c9acb1cc
55 changed files with 745 additions and 569 deletions

View File

@ -45,3 +45,11 @@ class AlarmsV2API(object):
@resource_api.Restify('/v2.0/alarms/{id}', method='get') @resource_api.Restify('/v2.0/alarms/{id}', method='get')
def do_get_alarm_by_id(self, req, res, id): def do_get_alarm_by_id(self, req, res, id):
res.status = '501 Not Implemented' res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarms/x', method='get')
def do_get_alarms_state_history(self, req, res):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarms/{id}/state-history', method='get')
def do_get_alarm_state_history(self, req, res, id):
res.status = '501 Not Implemented'

View File

@ -45,13 +45,3 @@ class V2API(object):
@resource_api.Restify('/v2.0/metrics/statistics', method='get') @resource_api.Restify('/v2.0/metrics/statistics', method='get')
def do_get_statistics(self, req, res): def do_get_statistics(self, req, res):
res.status = '501 Not Implemented' res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarms/state-history', method='get')
def do_get_alarms_state_history(self, req, res, id):
res.status = '501 Not Implemented'
@resource_api.Restify('/v2.0/alarms/{id}/state-history', method='get')
def do_get_alarm_state_history(self, req, res, id):
res.status = '501 Not Implemented'

View File

@ -14,14 +14,16 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from stevedore import driver
import os import os
from monasca.common import resource_api from wsgiref import simple_server
from monasca.openstack.common import log
from oslo.config import cfg from oslo.config import cfg
from oslo.config import types from oslo.config import types
from paste.deploy import loadapp import paste.deploy
from wsgiref import simple_server
from monasca.common import resource_api
from monasca.openstack.common import log
METRICS_DISPATCHER_NAMESPACE = 'monasca.metrics_dispatcher' METRICS_DISPATCHER_NAMESPACE = 'monasca.metrics_dispatcher'
ALARM_DEFINITIONS_DISPATCHER_NAMESPACE = 'monasca.alarm_definitions_dispatcher' ALARM_DEFINITIONS_DISPATCHER_NAMESPACE = 'monasca.alarm_definitions_dispatcher'
@ -183,6 +185,8 @@ def api_app(conf):
if __name__ == '__main__': if __name__ == '__main__':
wsgi_app = loadapp('config:etc/monasca.ini', relative_to=os.getcwd()) wsgi_app = (
paste.deploy.loadapp('config:etc/monasca.ini',
relative_to=os.getcwd()))
httpd = simple_server.make_server('127.0.0.1', 9000, wsgi_app) httpd = simple_server.make_server('127.0.0.1', 9000, wsgi_app)
httpd.serve_forever() httpd.serve_forever()

View File

@ -11,13 +11,13 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import time
from kafka import client from kafka import client
from kafka import common from kafka import common
from kafka import consumer from kafka import consumer
from kafka import producer from kafka import producer
from oslo.config import cfg from oslo.config import cfg
import time
try: try:
import ujson as json import ujson as json

View File

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

View File

@ -11,16 +11,16 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import time
from kafka import client from kafka import client
from kafka import common from kafka import common
from kafka import producer from kafka import producer
from oslo.config import cfg from oslo.config import cfg
import time
from monasca.openstack.common import log
from monasca.common.messaging import publisher
from monasca.common.messaging import exceptions from monasca.common.messaging import exceptions
from monasca.common.messaging import publisher
from monasca.openstack.common import log
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)

View File

@ -12,5 +12,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
def transform(events, tenant_id, region): def transform(events, tenant_id, region):
raise NotImplemented() raise NotImplemented()

View File

@ -12,5 +12,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
def transform(metrics, tenant_id, region): def transform(metrics, tenant_id, region):
raise NotImplemented() raise NotImplemented()

View File

@ -13,15 +13,17 @@
# under the License. # under the License.
from oslo.config import cfg from oslo.config import cfg
import monasca.common.messaging.message_formats.reference.events as reference_events
import monasca.common.messaging.message_formats.cadf.events as cadf_events import monasca.common.messaging.message_formats.cadf.events as cadf_events
import monasca.common.messaging.message_formats.identity.events as identity_events import monasca.common.messaging.message_formats.identity.events as ident_events
import monasca.common.messaging.message_formats.reference.events as ref_events
def create_events_transform(): def create_events_transform():
message_format = cfg.CONF.messaging.events_message_format message_format = cfg.CONF.messaging.events_message_format
if message_format == 'reference': if message_format == 'reference':
return reference_events.transform return ref_events.transform
elif message_format == 'cadf': elif message_format == 'cadf':
return cadf_events.transform return cadf_events.transform
else: else:
return identity_events.transform return ident_events.transform

View File

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

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from datetime import datetime
def transform(events, tenant_id, region): def transform(events, tenant_id, region):
return events return events

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from datetime import datetime
def transform(metrics, tenant_id, region): def transform(metrics, tenant_id, region):
return metrics return metrics

View File

@ -13,15 +13,17 @@
# under the License. # under the License.
from oslo.config import cfg from oslo.config import cfg
import monasca.common.messaging.message_formats.reference.metrics as reference_metrics
import monasca.common.messaging.message_formats.cadf.metrics as cadf_metrics import monasca.common.messaging.message_formats.cadf.metrics as cadf_metrics
import monasca.common.messaging.message_formats.identity.metrics as identity_metrics import monasca.common.messaging.message_formats.identity.metrics as id_metrics
import monasca.common.messaging.message_formats.reference.metrics as r_metrics
def create_metrics_transform(): def create_metrics_transform():
metrics_message_format = cfg.CONF.messaging.metrics_message_format metrics_message_format = cfg.CONF.messaging.metrics_message_format
if metrics_message_format == 'reference': if metrics_message_format == 'reference':
return reference_metrics.transform return r_metrics.transform
elif metrics_message_format == 'cadf': elif metrics_message_format == 'cadf':
return cadf_metrics.transform return cadf_metrics.transform
else: else:
return identity_metrics.transform return id_metrics.transform

View File

@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from datetime import datetime import datetime
def transform(event, tenant_id, region): def transform(event, tenant_id, region):

View File

@ -12,17 +12,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from datetime import datetime import datetime
def transform(metrics, tenant_id, region): def transform(metrics, tenant_id, region):
transformed_metric = { transformed_metric = {'metric': {},
'metric': {}, 'meta': {'tenantId': tenant_id, 'region': region},
'meta': { 'creation_time': datetime.now()}
'tenantId': tenant_id,
'region': region
},
'creation_time': datetime.now()
}
if isinstance(metrics, list): if isinstance(metrics, list):
transformed_metrics = [] transformed_metrics = []

View File

@ -13,16 +13,16 @@
# under the License. # under the License.
import abc import abc
import six import six
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class Publisher(object): class Publisher(object):
@abc.abstractmethod @abc.abstractmethod
def send_message(self, message): def send_message(self, message):
''' """Sends the message using the message queue.
Sends the message using the message queue.
:param message: Message to send. :param message: Message to send.
''' """
return return

View File

@ -12,20 +12,20 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import abc import abc
import six import six
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class AlarmDefinitionsRepository(object): class AlarmDefinitionsRepository(object):
def __init__(self): def __init__(self):
super(AlarmDefinitionsRepository, self).__init__() super(AlarmDefinitionsRepository, self).__init__()
@abc.abstractmethod @abc.abstractmethod
def create_alarm_definition(self, tenant_id, name, def create_alarm_definition(self, tenant_id, name, expression,
expression, sub_expr_list, description, severity, match_by, alarm_actions, sub_expr_list, description, severity, match_by,
undetermined_actions, ok_action): alarm_actions, undetermined_actions,
ok_action):
pass pass
@abc.abstractmethod @abc.abstractmethod

View File

@ -12,9 +12,33 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import abc import abc
import six import six
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class AlarmsRepository(object): class AlarmsRepository(object):
def __init__(self):
super(AlarmsRepository, self).__init__()
@abc.abstractmethod
def get_alarm_metrics(self, alarm_id):
pass
@abc.abstractmethod
def get_sub_alarms(self, tenant_id, alarm_id):
pass
@abc.abstractmethod
def delete_alarm(self, tenant_id, id):
pass
@abc.abstractmethod
def get_alarm(self, tenant_id, id):
pass
@abc.abstractmethod
def get_alarms(self, tenant_id, query_parms):
pass pass

View File

@ -13,6 +13,7 @@
# under the License. # under the License.
import abc import abc
import six import six

View File

@ -12,8 +12,10 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
class RepositoryException(Exception): class RepositoryException(Exception):
pass pass
class DoesNotExistException(RepositoryException): class DoesNotExistException(RepositoryException):
pass pass

View File

@ -12,13 +12,12 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import re import re
import time
import urllib import urllib
from time import strftime
from time import gmtime
from influxdb import InfluxDBClient from influxdb import client
from influxdb.client import InfluxDBClientError
from oslo.config import cfg from oslo.config import cfg
from monasca.common.repositories import exceptions from monasca.common.repositories import exceptions
@ -30,11 +29,12 @@ LOG = log.getLogger(__name__)
class MetricsRepository(metrics_repository.MetricsRepository): class MetricsRepository(metrics_repository.MetricsRepository):
def __init__(self): def __init__(self):
try: try:
self.conf = cfg.CONF self.conf = cfg.CONF
self.influxdb_client = InfluxDBClient( self.influxdb_client = client.InfluxDBClient(
self.conf.influxdb.ip_address, self.conf.influxdb.port, self.conf.influxdb.ip_address, self.conf.influxdb.port,
self.conf.influxdb.user, self.conf.influxdb.password, self.conf.influxdb.user, self.conf.influxdb.password,
self.conf.influxdb.database_name) self.conf.influxdb.database_name)
@ -144,8 +144,8 @@ class MetricsRepository(metrics_repository.MetricsRepository):
def _decode_influxdb_serie_name_list(self, series_names): def _decode_influxdb_serie_name_list(self, series_names):
""" """Example series_names from InfluxDB.
Example series_names from InfluxDB.
[ [
{ {
"points": [ "points": [
@ -178,11 +178,11 @@ class MetricsRepository(metrics_repository.MetricsRepository):
return json_metric_list return json_metric_list
def _decode_influxdb_serie_name(self, serie_name): def _decode_influxdb_serie_name(self, serie_name):
""" """Decodes a serie name from InfluxDB.
Decodes a serie name from InfluxDB. The raw serie name is
The raw serie name is
formed by url encoding the name, tenant id, region, and dimensions, formed by url encoding the name, tenant id, region, and dimensions,
and concatenating them into a quasi URL query string. and concatenating them into a quasi URL query string.
@ -227,11 +227,10 @@ class MetricsRepository(metrics_repository.MetricsRepository):
return metric return metric
def measurement_list(self, tenant_id, name, dimensions, start_timestamp, def measurement_list(self, tenant_id, name, dimensions, start_timestamp,
end_timestamp): end_timestamp):
""" """Example result from InfluxDB.
Example result from InfluxDB.
[ [
{ {
"points": [ "points": [
@ -285,9 +284,10 @@ class MetricsRepository(metrics_repository.MetricsRepository):
try: try:
result = self.influxdb_client.query(query, 's') result = self.influxdb_client.query(query, 's')
except InfluxDBClientError as ex: except client.InfluxDBClientError as ex:
if ex.code == 400 and ex.content == 'Couldn\'t look up ' \ # check for non-existent serie name.
'columns': msg = "Couldn't look up columns"
if ex.code == 400 and ex.content == (msg):
return json_measurement_list return json_measurement_list
else: else:
raise ex raise ex
@ -307,8 +307,9 @@ class MetricsRepository(metrics_repository.MetricsRepository):
columns] columns]
# format the utc date in the points # format the utc date in the points
fmtd_pts = [[strftime("%Y-%m-%dT%H:%M:%SZ", gmtime(point[0])), fmtd_pts = [[time.strftime("%Y-%m-%dT%H:%M:%SZ",
point[1], point[2]] for point in serie['points']] time.gmtime(point[0])), point[1],
point[2]] for point in serie['points']]
measurement = {"name": metric['name'], measurement = {"name": metric['name'],
"dimensions": metric['dimensions'], "dimensions": metric['dimensions'],
@ -322,7 +323,6 @@ class MetricsRepository(metrics_repository.MetricsRepository):
LOG.exception(ex) LOG.exception(ex)
raise exceptions.RepositoryException(ex) raise exceptions.RepositoryException(ex)
def metrics_statistics(self, tenant_id, name, dimensions, start_timestamp, def metrics_statistics(self, tenant_id, name, dimensions, start_timestamp,
end_timestamp, statistics, period): end_timestamp, statistics, period):
@ -336,9 +336,10 @@ class MetricsRepository(metrics_repository.MetricsRepository):
try: try:
result = self.influxdb_client.query(query, 's') result = self.influxdb_client.query(query, 's')
except InfluxDBClientError as ex: except client.InfluxDBClientError as ex:
if ex.code == 400 and ex.content == 'Couldn\'t look up ' \ # check for non-existent serie name.
'columns': msg = "Couldn't look up columns"
if ex.code == 400 and ex.content == (msg):
return json_statistics_list return json_statistics_list
else: else:
raise ex raise ex
@ -357,8 +358,9 @@ class MetricsRepository(metrics_repository.MetricsRepository):
columns = [column.replace('time', 'timestamp') for column in columns = [column.replace('time', 'timestamp') for column in
columns] columns]
fmtd_pts_list_list = [[strftime("%Y-%m-%dT%H:%M:%SZ", fmtd_pts_list_list = [[time.strftime("%Y-%m-%dT%H:%M:%SZ",
gmtime(pts_list[0]))] + pts_list[1:] time.gmtime(pts_list[
0]))] + pts_list[1:]
for pts_list in serie['points']] for pts_list in serie['points']]
measurement = {"name": metric['name'], measurement = {"name": metric['name'],
@ -373,3 +375,118 @@ class MetricsRepository(metrics_repository.MetricsRepository):
except Exception as ex: except Exception as ex:
LOG.exception(ex) LOG.exception(ex)
raise exceptions.RepositoryException(ex) raise exceptions.RepositoryException(ex)
def alarm_history(self, tenant_id, alarm_id_list, start_timestamp=None,
end_timestamp=None):
"""Example result from Influxdb.
[
{
"points": [
[
1415894490,
272140001,
"6ac10841-d02f-4f7d-a191-ae0a3d9a25f2",
"[{\"name\": \"cpu.system_perc\", \"dimensions\": {
\"hostname\": \"mini-mon\", \"component\":
\"monasca-agent\", \"service\": \"monitoring\"}},
{\"name\": \"load.avg_1_min\", \"dimensions\": {
\"hostname\": \"mini-mon\", \"component\":
\"monasca-agent\", \"service\": \"monitoring\"}}]",
"ALARM",
"OK",
"Thresholds were exceeded for the sub-alarms: [max(
load.avg_1_min{hostname=mini-mon}) > 0.0,
max(cpu.system_perc) > 0.0]",
"{}"
],
],
"name": "alarm_state_history",
"columns": [
"time",
"sequence_number",
"alarm_id",
"metrics",
"new_state",
"old_state",
"reason",
"reason_data"
]
}
]
:param tenant_id:
:param alarm_id:
:return:
"""
try:
json_alarm_history_list = []
if not alarm_id_list:
return json_alarm_history_list
for alarm_id in alarm_id_list:
if '\'' in alarm_id or ';' in alarm_id:
raise Exception(
"Input from user contains single quote ['] or "
"semi-colon [;] characters[ {} ]".format(alarm_id))
query = """
select alarm_id, metrics, old_state, new_state,
reason, reason_data
from alarm_state_history
"""
where_clause = (
" where tenant_id = '{}' ".format(tenant_id.encode('utf8')))
alarm_id_where_clause_list = (
[" alarm_id = '{}' ".format(id.encode('utf8'))
for id in alarm_id_list])
alarm_id_where_clause = " or ".join(alarm_id_where_clause_list)
where_clause += ' and (' + alarm_id_where_clause + ')'
time_clause = ''
if start_timestamp:
# subtract 1 from timestamp to get >= semantics
time_clause += " and time > " + str(start_timestamp - 1) + "s"
if end_timestamp:
# add 1 to timestamp to get <= semantics
time_clause += " and time < " + str(end_timestamp + 1) + "s"
query += where_clause + time_clause
try:
result = self.influxdb_client.query(query, 's')
except client.InfluxDBClientError as ex:
# check for non-existent serie name. only happens
# if alarm_state_history serie does not exist.
msg = "Couldn't look up columns"
if ex.code == 400 and ex.content == (msg):
return json_alarm_history_list
else:
raise ex
if not result:
return json_alarm_history_list
# There's only one serie, alarm_state_history.
for point in result[0]['points']:
alarm_point = {u'alarm_id': point[2],
u'metrics': json.loads(point[3]),
u'old_state': point[4], u'new_state': point[5],
u'reason': point[6], u'reason_data': point[7],
u'timestamp': time.strftime(
"%Y-%m-%dT%H:%M:%SZ",
time.gmtime(point[0]))}
json_alarm_history_list.append(alarm_point)
return json_alarm_history_list
except Exception as ex:
LOG.exception(ex)
raise exceptions.RepositoryException(ex)

View File

@ -30,5 +30,5 @@ class MetricsRepository(object):
@abc.abstractmethod @abc.abstractmethod
def metrics_statistics(self, tenant_id, name, dimensions, start_timestamp, def metrics_statistics(self, tenant_id, name, dimensions, start_timestamp,
end_timestamp, statistics): end_timestamp, statistics, period):
pass pass

View File

@ -13,20 +13,19 @@
# under the License. # under the License.
import datetime import datetime
from monasca.common.repositories import alarm_definitions_repository from monasca.common.repositories import alarm_definitions_repository as adr
from monasca.common.repositories.exceptions import DoesNotExistException from monasca.common.repositories import exceptions
from monasca.common.repositories.mysql.mysql_repository import MySQLRepository from monasca.common.repositories.mysql import mysql_repository
from monasca.common.repositories.mysql.mysql_repository import mysql_try_catch_block
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.openstack.common import uuidutils from monasca.openstack.common import uuidutils
from monasca.common.repositories import exceptions
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class AlarmDefinitionsRepository(MySQLRepository, class AlarmDefinitionsRepository(mysql_repository.MySQLRepository,
alarm_definitions_repository.AlarmDefinitionsRepository): adr.AlarmDefinitionsRepository):
base_query = """ base_query = """
select ad.id, ad.name, ad.description, ad.expression, select ad.id, ad.name, ad.description, ad.expression,
ad.match_by, ad.severity, ad.actions_enabled, ad.match_by, ad.severity, ad.actions_enabled,
@ -56,7 +55,7 @@ class AlarmDefinitionsRepository(MySQLRepository,
super(AlarmDefinitionsRepository, self).__init__() super(AlarmDefinitionsRepository, self).__init__()
@mysql_try_catch_block @mysql_repository.mysql_try_catch_block
def get_alarm_definition(self, tenant_id, id): def get_alarm_definition(self, tenant_id, id):
parms = [tenant_id, id] parms = [tenant_id, id]
@ -72,9 +71,9 @@ class AlarmDefinitionsRepository(MySQLRepository,
if rows: if rows:
return rows[0] return rows[0]
else: else:
raise DoesNotExistException raise exceptions.DoesNotExistException
@mysql_try_catch_block @mysql_repository.mysql_try_catch_block
def get_alarm_definitions(self, tenant_id, name, dimensions): def get_alarm_definitions(self, tenant_id, name, dimensions):
parms = [tenant_id] parms = [tenant_id]
@ -113,8 +112,7 @@ class AlarmDefinitionsRepository(MySQLRepository,
return self._execute_query(query, parms) return self._execute_query(query, parms)
@mysql_repository.mysql_try_catch_block
@mysql_try_catch_block
def get_sub_alarms(self, tenant_id, alarm_definition_id): def get_sub_alarms(self, tenant_id, alarm_definition_id):
parms = [tenant_id, alarm_definition_id] parms = [tenant_id, alarm_definition_id]
@ -131,7 +129,7 @@ class AlarmDefinitionsRepository(MySQLRepository,
return self._execute_query(query, parms) return self._execute_query(query, parms)
@mysql_try_catch_block @mysql_repository.mysql_try_catch_block
def get_alarm_metrics(self, tenant_id, alarm_definition_id): def get_alarm_metrics(self, tenant_id, alarm_definition_id):
parms = [tenant_id, alarm_definition_id] parms = [tenant_id, alarm_definition_id]
@ -156,7 +154,7 @@ class AlarmDefinitionsRepository(MySQLRepository,
return self._execute_query(query, parms) return self._execute_query(query, parms)
@mysql_try_catch_block @mysql_repository.mysql_try_catch_block
def delete_alarm_definition(self, tenant_id, alarm_definition_id): def delete_alarm_definition(self, tenant_id, alarm_definition_id):
"""Soft delete the alarm definition. """Soft delete the alarm definition.
@ -187,7 +185,7 @@ class AlarmDefinitionsRepository(MySQLRepository,
return True return True
@mysql_try_catch_block @mysql_repository.mysql_try_catch_block
def get_sub_alarm_definitions(self, alarm_definition_id): def get_sub_alarm_definitions(self, alarm_definition_id):
parms = [alarm_definition_id] parms = [alarm_definition_id]
@ -206,7 +204,7 @@ class AlarmDefinitionsRepository(MySQLRepository,
return self._execute_query(query, parms) return self._execute_query(query, parms)
@mysql_try_catch_block @mysql_repository.mysql_try_catch_block
def create_alarm_definition(self, tenant_id, name, expression, def create_alarm_definition(self, tenant_id, name, expression,
sub_expr_list, description, severity, match_by, sub_expr_list, description, severity, match_by,
alarm_actions, undetermined_actions, alarm_actions, undetermined_actions,
@ -275,7 +273,6 @@ class AlarmDefinitionsRepository(MySQLRepository,
return alarm_definition_id return alarm_definition_id
def _insert_into_alarm_action(self, cursor, alarm_definition_id, actions, def _insert_into_alarm_action(self, cursor, alarm_definition_id, actions,
alarm_state): alarm_state):
for action in actions: for action in actions:
@ -293,5 +290,3 @@ class AlarmDefinitionsRepository(MySQLRepository,
action_id) action_id)
values(?,?,?)""", alarm_definition_id, values(?,?,?)""", alarm_definition_id,
alarm_state.encode('utf8'), action.encode('utf8')) alarm_state.encode('utf8'), action.encode('utf8'))

View File

@ -11,18 +11,18 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from monasca.common.repositories.exceptions import DoesNotExistException
from monasca.common.repositories import alarms_repository from monasca.common.repositories import alarms_repository
from monasca.common.repositories.mysql.mysql_repository import MySQLRepository from monasca.common.repositories import exceptions
from monasca.common.repositories.mysql.mysql_repository import mysql_try_catch_block from monasca.common.repositories.mysql import mysql_repository
from monasca.openstack.common import log from monasca.openstack.common import log
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class AlarmsRepository(MySQLRepository, alarms_repository.AlarmsRepository): class AlarmsRepository(mysql_repository.MySQLRepository,
alarms_repository.AlarmsRepository):
base_query = """ base_query = """
select distinct a.id as alarm_id, a.state, select distinct a.id as alarm_id, a.state,
@ -47,7 +47,7 @@ class AlarmsRepository(MySQLRepository, alarms_repository.AlarmsRepository):
super(AlarmsRepository, self).__init__() super(AlarmsRepository, self).__init__()
@mysql_try_catch_block @mysql_repository.mysql_try_catch_block
def get_alarm_metrics(self, alarm_id): def get_alarm_metrics(self, alarm_id):
parms = [alarm_id] parms = [alarm_id]
@ -70,7 +70,7 @@ class AlarmsRepository(MySQLRepository, alarms_repository.AlarmsRepository):
return self._execute_query(query, parms) return self._execute_query(query, parms)
@mysql_try_catch_block @mysql_repository.mysql_try_catch_block
def get_sub_alarms(self, tenant_id, alarm_id): def get_sub_alarms(self, tenant_id, alarm_id):
parms = [tenant_id, alarm_id] parms = [tenant_id, alarm_id]
@ -87,7 +87,7 @@ class AlarmsRepository(MySQLRepository, alarms_repository.AlarmsRepository):
return self._execute_query(query, parms) return self._execute_query(query, parms)
@mysql_try_catch_block @mysql_repository.mysql_try_catch_block
def delete_alarm(self, tenant_id, id): def delete_alarm(self, tenant_id, id):
parms = [tenant_id, id] parms = [tenant_id, id]
@ -109,11 +109,11 @@ class AlarmsRepository(MySQLRepository, alarms_repository.AlarmsRepository):
cursor.execute(query, parms) cursor.execute(query, parms)
if cursor.rowcount < 1: if cursor.rowcount < 1:
raise DoesNotExistException raise exceptions.DoesNotExistException
self._commit_close_cnxn(cnxn) self._commit_close_cnxn(cnxn)
@mysql_try_catch_block @mysql_repository.mysql_try_catch_block
def get_alarm(self, tenant_id, id): def get_alarm(self, tenant_id, id):
parms = [tenant_id, id] parms = [tenant_id, id]
@ -128,11 +128,11 @@ class AlarmsRepository(MySQLRepository, alarms_repository.AlarmsRepository):
rows = self._execute_query(query, parms) rows = self._execute_query(query, parms)
if not rows: if not rows:
raise DoesNotExistException raise exceptions.DoesNotExistException
else: else:
return rows return rows
@mysql_try_catch_block @mysql_repository.mysql_try_catch_block
def get_alarms(self, tenant_id, query_parms): def get_alarms(self, tenant_id, query_parms):
parms = [tenant_id] parms = [tenant_id]

View File

@ -12,10 +12,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from monasca.openstack.common import log
from oslo.config import cfg from oslo.config import cfg
import peewee import peewee
from monasca.openstack.common import log
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
db = peewee.MySQLDatabase(cfg.CONF.mysql.database_name, db = peewee.MySQLDatabase(cfg.CONF.mysql.database_name,

View File

@ -11,27 +11,22 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import datetime
import pyodbc
from monasca.common.repositories.exceptions import DoesNotExistException
from oslo.config import cfg from oslo.config import cfg
import pyodbc
from monasca.common.repositories import alarms_repository
from monasca.openstack.common import log
from monasca.openstack.common import uuidutils
from monasca.common.repositories import exceptions from monasca.common.repositories import exceptions
from monasca.openstack.common import log
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class MySQLRepository(object): class MySQLRepository(object):
database_driver = 'MySQL ODBC 5.3 ANSI Driver' database_driver = 'MySQL ODBC 5.3 ANSI Driver'
database_cnxn_template = 'DRIVER={' \ database_cnxn_template = ('DRIVER={'
'%s};Server=%s;CHARSET=UTF8;Database=%s;Uid=%s' \ '%s};Server=%s;CHARSET=UTF8;Database=%s;Uid=%s'
';Pwd=%s' ';Pwd=%s')
def __init__(self): def __init__(self):
@ -65,7 +60,6 @@ class MySQLRepository(object):
cnxn.commit() cnxn.commit()
cnxn.close() cnxn.close()
def _execute_query(self, query, parms): def _execute_query(self, query, parms):
cnxn, cursor = self._get_cnxn_cursor_tuple() cnxn, cursor = self._get_cnxn_cursor_tuple()
@ -87,7 +81,7 @@ def mysql_try_catch_block(fun):
return fun(*args, **kwargs) return fun(*args, **kwargs)
except DoesNotExistException: except exceptions.DoesNotExistException:
raise raise
except Exception as ex: except Exception as ex:
LOG.exception(ex) LOG.exception(ex)

View File

@ -13,11 +13,14 @@
# under the License. # under the License.
import datetime import datetime
from monasca.common.repositories import notifications_repository
from monasca.common.repositories import exceptions
from monasca.openstack.common import log
import peewee
import model import model
import peewee
from monasca.common.repositories import exceptions
from monasca.common.repositories import notifications_repository
from monasca.openstack.common import log
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -55,7 +58,7 @@ class NotificationsRepository(
self, id, tenant_id, name, notification_type, address): self, id, tenant_id, name, notification_type, address):
try: try:
now = datetime.datetime.utcnow() now = datetime.datetime.utcnow()
q = Notification_Method.create( Notification_Method.create(
id=id, id=id,
tenant_id=tenant_id, tenant_id=tenant_id,
name=name, name=name,
@ -73,7 +76,6 @@ class NotificationsRepository(
Notification_Method.tenant_id == tenant_id) Notification_Method.tenant_id == tenant_id)
results = q.execute() results = q.execute()
notifications = []
notifications = [ notifications = [
self.notification_from_result(result) for result in results] self.notification_from_result(result) for result in results]
return notifications return notifications
@ -82,7 +84,6 @@ class NotificationsRepository(
raise exceptions.RepositoryException(ex) raise exceptions.RepositoryException(ex)
def delete_notification(self, tenant_id, notification_id): def delete_notification(self, tenant_id, notification_id):
num_rows_deleted = 0
try: try:
q = Notification_Method.delete().where( q = Notification_Method.delete().where(
@ -113,7 +114,6 @@ class NotificationsRepository(
def update_notification( def update_notification(
self, id, tenant_id, name, notification_type, address): self, id, tenant_id, name, notification_type, address):
now = datetime.datetime.utcnow() now = datetime.datetime.utcnow()
num_rows_updated = 0
try: try:
q = Notification_Method.update( q = Notification_Method.update(
name=name, name=name,

View File

@ -12,11 +12,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from monasca.common.repositories import transforms_repository
from monasca.common.repositories import exceptions
from monasca.openstack.common import log
import peewee
import model import model
import peewee
from monasca.common.repositories import exceptions
from monasca.common.repositories import transforms_repository
from monasca.openstack.common import log
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -32,11 +34,14 @@ class Transform(model.Model):
updated_at = peewee.DateTimeField() updated_at = peewee.DateTimeField()
deleted_at = peewee.DateTimeField() deleted_at = peewee.DateTimeField()
class TransformsRepository(transforms_repository.TransformsRepository):
def create_transforms(self, id, tenant_id, name, description, specification, enabled): class TransformsRepository(transforms_repository.TransformsRepository):
def create_transforms(self, id, tenant_id, name, description,
specification, enabled):
try: try:
q = Transform.create(id=id, tenant_id=tenant_id, name=name, description=description, specification=specification, enabled=enabled) q = Transform.create(id=id, tenant_id=tenant_id, name=name,
description=description,
specification=specification, enabled=enabled)
q.save() q.save()
except Exception as ex: except Exception as ex:
LOG.exception(str(ex)) LOG.exception(str(ex))
@ -49,13 +54,10 @@ class TransformsRepository(transforms_repository.TransformsRepository):
transforms = [] transforms = []
for result in results: for result in results:
transform = { transform = {'id': result.id, 'name': result.name,
'id': result.id,
'name': result.name,
'description': result.description, 'description': result.description,
'specification': result.specification, 'specification': result.specification,
'enabled': result.enabled 'enabled': result.enabled}
}
transforms.append(transform) transforms.append(transform)
return transforms return transforms
except Exception as ex: except Exception as ex:
@ -66,7 +68,8 @@ class TransformsRepository(transforms_repository.TransformsRepository):
num_rows_deleted = 0 num_rows_deleted = 0
try: try:
q = Transform.delete().where((Transform.tenant_id == tenant_id) & (Transform.id == transform_id)) q = Transform.delete().where((Transform.tenant_id == tenant_id) & (
Transform.id == transform_id))
num_rows_deleted = q.execute() num_rows_deleted = q.execute()
except Exception as ex: except Exception as ex:
LOG.exception(str(ex)) LOG.exception(str(ex))

View File

@ -13,6 +13,7 @@
# under the License. # under the License.
import abc import abc
import six import six

View File

@ -13,14 +13,15 @@
# under the License. # under the License.
import abc import abc
import six import six
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class TransformsRepository(object): class TransformsRepository(object):
@abc.abstractmethod @abc.abstractmethod
def create_transforms(self, id, tenant_id, name, description, specification, enabled): def create_transforms(self, id, tenant_id, name, description,
specification, enabled):
return return
@abc.abstractmethod @abc.abstractmethod

View File

@ -16,9 +16,9 @@
import falcon import falcon
from falcon import api_helpers from falcon import api_helpers
from stevedore import driver
from monasca.openstack.common import log from monasca.openstack.common import log
from stevedore import driver
RESOURCE_METHOD_FLAG = 'fab05a04-b861-4651-bd0c-9cb3eb9a6088' RESOURCE_METHOD_FLAG = 'fab05a04-b861-4651-bd0c-9cb3eb9a6088'
@ -32,16 +32,14 @@ def init_driver(namespace, driver_name, drv_invoke_args=()):
:param driver_name: the driver name (in monasca.conf) :param driver_name: the driver name (in monasca.conf)
:param invoke_args: args to pass to the driver (a tuple) :param invoke_args: args to pass to the driver (a tuple)
""" """
mgr = driver.DriverManager( mgr = driver.DriverManager(namespace=namespace, name=driver_name,
namespace = namespace, invoke_on_load=True,
name = driver_name, invoke_args=drv_invoke_args)
invoke_on_load = True,
invoke_args = drv_invoke_args
)
return mgr.driver return mgr.driver
class Restify(object): class Restify(object):
def __init__(self, path='', method='GET'): def __init__(self, path='', method='GET'):
if not path: if not path:
raise Exception('Path has to be specified.') raise Exception('Path has to be specified.')

View File

@ -15,6 +15,7 @@
# under the License. # under the License.
import abc import abc
import six import six

View File

@ -16,22 +16,13 @@
import itertools import itertools
import sys import sys
from pyparsing import CaselessLiteral import pyparsing
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): class SubExpr(object):
def __init__(self, tokens): def __init__(self, tokens):
self._sub_expr = tokens self._sub_expr = tokens
self._func = tokens.func self._func = tokens.func
self._metric_name = tokens.metric_name self._metric_name = tokens.metric_name
@ -173,67 +164,70 @@ class BinaryOp(object):
class AndSubExpr(BinaryOp): class AndSubExpr(BinaryOp):
""" Expand later as needed. """Expand later as needed."""
"""
pass pass
class OrSubExpr(BinaryOp): class OrSubExpr(BinaryOp):
"""Expand later as needed. """Expand later as needed."""
"""
pass pass
COMMA = Literal(",") COMMA = pyparsing.Literal(",")
LPAREN = Literal("(") LPAREN = pyparsing.Literal("(")
RPAREN = Literal(")") RPAREN = pyparsing.Literal(")")
EQUAL = Literal("=") EQUAL = pyparsing.Literal("=")
LBRACE = Literal("{") LBRACE = pyparsing.Literal("{")
RBRACE = Literal("}") RBRACE = pyparsing.Literal("}")
# Initialize non-ascii unicode code points in the Basic Multilingual Plane. # Initialize non-ascii unicode code points in the Basic Multilingual Plane.
unicode_printables = u''.join( unicode_printables = u''.join(
unichr(c) for c in xrange(128, 65536) if not unichr(c).isspace()) unichr(c) for c in xrange(128, 65536) if not unichr(c).isspace())
# Does not like comma. No Literals from above allowed. # Does not like comma. No Literals from above allowed.
valid_identifier_chars = (unicode_printables + alphanums + ".-_#!$%&'*+/:;?@[" valid_identifier_chars = (
"\\]^`|~") (unicode_printables + pyparsing.alphanums + ".-_#!$%&'*+/:;?@[\\]^`|~"))
metric_name = Word(valid_identifier_chars, min=1, max=255)("metric_name") metric_name = (
dimension_name = Word(valid_identifier_chars, min=1, max=255) pyparsing.Word(valid_identifier_chars, min=1, max=255)("metric_name"))
dimension_value = Word(valid_identifier_chars, min=1, max=255) dimension_name = pyparsing.Word(valid_identifier_chars, min=1, max=255)
dimension_value = pyparsing.Word(valid_identifier_chars, min=1, max=255)
integer_number = Word(nums) integer_number = pyparsing.Word(pyparsing.nums)
decimal_number = Word(nums + ".") decimal_number = pyparsing.Word(pyparsing.nums + ".")
max = CaselessLiteral("max") max = pyparsing.CaselessLiteral("max")
min = CaselessLiteral("min") min = pyparsing.CaselessLiteral("min")
avg = CaselessLiteral("avg") avg = pyparsing.CaselessLiteral("avg")
count = CaselessLiteral("count") count = pyparsing.CaselessLiteral("count")
sum = CaselessLiteral("sum") sum = pyparsing.CaselessLiteral("sum")
func = (max | min | avg | count | sum)("func") func = (max | min | avg | count | sum)("func")
less_than_op = (CaselessLiteral("<") | CaselessLiteral("lt")) less_than_op = (
less_than_eq_op = (CaselessLiteral("<=") | CaselessLiteral("lte")) (pyparsing.CaselessLiteral("<") | pyparsing.CaselessLiteral("lt")))
greater_than_op = (CaselessLiteral(">") | CaselessLiteral("gt")) less_than_eq_op = (
greater_than_eq_op = (CaselessLiteral(">=") | CaselessLiteral("gte")) (pyparsing.CaselessLiteral("<=") | pyparsing.CaselessLiteral("lte")))
greater_than_op = (
(pyparsing.CaselessLiteral(">") | pyparsing.CaselessLiteral("gt")))
greater_than_eq_op = (
(pyparsing.CaselessLiteral(">=") | pyparsing.CaselessLiteral("gte")))
# Order is important. Put longer prefix first. # Order is important. Put longer prefix first.
relational_op = ( relational_op = (
less_than_eq_op | less_than_op | greater_than_eq_op | greater_than_op)( less_than_eq_op | less_than_op | greater_than_eq_op | greater_than_op)(
"relational_op") "relational_op")
AND = CaselessLiteral("and") | CaselessLiteral("&&") AND = pyparsing.CaselessLiteral("and") | pyparsing.CaselessLiteral("&&")
OR = CaselessLiteral("or") | CaselessLiteral("||") OR = pyparsing.CaselessLiteral("or") | pyparsing.CaselessLiteral("||")
logical_op = (AND | OR)("logical_op") logical_op = (AND | OR)("logical_op")
times = CaselessLiteral("times") times = pyparsing.CaselessLiteral("times")
dimension = Group(dimension_name + EQUAL + dimension_value) dimension = pyparsing.Group(dimension_name + EQUAL + dimension_value)
# Cannot have any whitespace after the comma delimiter. # Cannot have any whitespace after the comma delimiter.
dimension_list = Group(Optional( dimension_list = pyparsing.Group(pyparsing.Optional(
LBRACE + delimitedList(dimension, delim=',', combine=True)( LBRACE + pyparsing.delimitedList(dimension, delim=',', combine=True)(
"dimensions_list") + RBRACE)) "dimensions_list") + RBRACE))
metric = metric_name + dimension_list("dimensions") metric = metric_name + dimension_list("dimensions")
@ -241,17 +235,18 @@ period = integer_number("period")
threshold = decimal_number("threshold") threshold = decimal_number("threshold")
periods = integer_number("periods") periods = integer_number("periods")
expression = Forward() expression = pyparsing.Forward()
sub_expression = (func + LPAREN + metric + Optional( sub_expression = (func + LPAREN + metric + pyparsing.Optional(
COMMA + period) + RPAREN + relational_op + threshold + Optional( COMMA + period) + RPAREN + relational_op + threshold + pyparsing.Optional(
times + periods) | LPAREN + expression + RPAREN) times + periods) | LPAREN + expression + RPAREN)
sub_expression.setParseAction(SubExpr) sub_expression.setParseAction(SubExpr)
expression = operatorPrecedence(sub_expression, expression = (
[(AND, 2, opAssoc.LEFT, AndSubExpr), pyparsing.operatorPrecedence(sub_expression,
(OR, 2, opAssoc.LEFT, OrSubExpr)]) [(AND, 2, pyparsing.opAssoc.LEFT, AndSubExpr),
(OR, 2, pyparsing.opAssoc.LEFT, OrSubExpr)]))
class AlarmExprParser(object): class AlarmExprParser(object):
@ -262,37 +257,37 @@ class AlarmExprParser(object):
def sub_expr_list(self): def sub_expr_list(self):
# Remove all spaces before parsing. Simple, quick fix for whitespace # Remove all spaces before parsing. Simple, quick fix for whitespace
# issue with dimension list not allowing whitespace after comma. # issue with dimension list not allowing whitespace after comma.
parseResult = (expression + stringEnd).parseString( parseResult = (expression + pyparsing.stringEnd).parseString(
self._expr.replace(' ', '')) self._expr.replace(' ', ''))
sub_expr_list = parseResult[0].operands_list sub_expr_list = parseResult[0].operands_list
return sub_expr_list return sub_expr_list
def main(): def main():
""" Used for development and testing. """ """Used for development and testing."""
expr0 = "max(-_.千幸福的笑脸{घोड़ा=馬, dn2=dv2}, 60) gte 100 times 3 && " \ expr0 = ("max(-_.千幸福的笑脸{घोड़ा=馬, dn2=dv2}, 60) gte 100 times 3 && "
"(min(ເຮືອນ{dn3=dv3,家=дом}) < 10 or sum(biz{dn5=dv5}) > 99 and " \ "(min(ເຮືອນ{dn3=dv3,家=дом}) < 10 or sum(biz{dn5=dv5}) >9 9and "
"count(fizzle) lt 0 or count(baz) > 1)".decode('utf8') "count(fizzle) lt 0 or count(baz) > 1)".decode('utf8'))
expr1 = "max(foo{hostname=mini-mon,千=千}, 120) > 100 and (max(bar)>100 \ expr1 = ("max(foo{hostname=mini-mon,千=千}, 120) > 100 and (max(bar)>100 "
or max(biz)>100)".decode('utf8') " or max(biz)>100)".decode('utf8'))
expr2 = "max(foo)>=100" expr2 = "max(foo)>=100"
for expr in (expr0, expr1, expr2): for expr in (expr0, expr1, expr2):
print 'orig expr: {}'.format(expr.encode('utf8')) print ('orig expr: {}'.format(expr.encode('utf8')))
alarmExprParser = AlarmExprParser(expr) alarmExprParser = AlarmExprParser(expr)
sub_expr = alarmExprParser.sub_expr_list sub_expr = alarmExprParser.sub_expr_list
for sub_expression in sub_expr: for sub_expression in sub_expr:
print 'sub expr: {}'.format( print ('sub expr: {}'.format(
sub_expression.sub_expr_str.encode('utf8')) sub_expression.sub_expr_str.encode('utf8')))
print 'fmtd sub expr: {}'.format( print ('fmtd sub expr: {}'.format(
sub_expression.fmtd_sub_expr_str.encode('utf8')) sub_expression.fmtd_sub_expr_str.encode('utf8')))
print 'sub_expr dimensions: {}'.format( print ('sub_expr dimensions: {}'.format(
sub_expression.dimensions_str.encode('utf8')) sub_expression.dimensions_str.encode('utf8')))
print print ()
print print ()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -14,11 +14,13 @@
class MockAuthFilter(object): class MockAuthFilter(object):
''' """Authorization filter.
This authorization filter doesn't do any authentication, it just copies the This authorization filter doesn't do any authentication, it just copies the
auth token to the tenant ID and supplies the 'admin' role and is meant for auth token to the tenant ID and supplies the 'admin' role and is meant for
testing purposes only. testing purposes only.
''' """
def __init__(self, app, conf): def __init__(self, app, conf):
self.app = app self.app = app
self.conf = conf self.conf = conf

View File

@ -1,87 +0,0 @@
# Copyright 2013 IBM Corp
#
# Author: Tong Li <litong01@us.ibm.com>
#
# 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 logging
import os
import tempfile
import fixtures
import testtools
_TRUE_VALUES = ('True', 'true', '1', 'yes')
_LOG_FORMAT = "%(levelname)8s [%(name)s] %(message)s"
class BaseTestCase(testtools.TestCase):
def setUp(self):
super(BaseTestCase, self).setUp()
self._set_timeout()
self._fake_output()
self._fake_logs()
self.useFixture(fixtures.NestedTempfile())
self.useFixture(fixtures.TempHomeDir())
self.tempdirs = []
def _set_timeout(self):
test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
try:
test_timeout = int(test_timeout)
except ValueError:
# If timeout value is invalid do not set a timeout.
test_timeout = 0
if test_timeout > 0:
self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
def _fake_output(self):
if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES:
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES:
stderr = self.useFixture(fixtures.StringStream('stderr')).stream
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
def _fake_logs(self):
if os.environ.get('OS_DEBUG') in _TRUE_VALUES:
level = logging.DEBUG
else:
level = logging.INFO
capture_logs = os.environ.get('OS_LOG_CAPTURE') in _TRUE_VALUES
if capture_logs:
self.useFixture(
fixtures.FakeLogger(
format=_LOG_FORMAT,
level=level,
nuke_handlers=capture_logs,
)
)
else:
logging.basicConfig(format=_LOG_FORMAT, level=level)
def create_tempfiles(self, files, ext='.conf'):
tempfiles = []
for (basename, contents) in files:
if not os.path.isabs(basename):
(fd, path) = tempfile.mkstemp(prefix=basename, suffix=ext)
else:
path = basename + ext
fd = os.open(path, os.O_CREAT | os.O_WRONLY)
tempfiles.append(path)
try:
os.write(fd, contents)
finally:
os.close(fd)
return tempfiles

View File

@ -0,0 +1,21 @@
# 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 unittest
class Test_first(unittest.TestCase):
def test_first(self):
assert 1 == 1

View File

@ -12,8 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from voluptuous import Schema, Length, Optional import voluptuous
from voluptuous import Required, Any, All
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.v2.common.schemas import exceptions from monasca.v2.common.schemas import exceptions
@ -22,19 +21,25 @@ from monasca.v2.common.schemas import exceptions
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
alarm_definition_schema = { alarm_definition_schema = {
Required('name'): All(Any(str, unicode), Length(max=250)), voluptuous.Required('name'): voluptuous.All(voluptuous.Any(str, unicode),
Required('expression'): All(Any(str, unicode), Length(max=4096)), voluptuous.Length(max=250)),
Optional('description'): All(Any(str, unicode), Length(max=250)), voluptuous.Required('expression'): voluptuous.All(
Optional('severity'): All( voluptuous.Any(str, unicode), voluptuous.Length(max=4096)),
Any('low', 'medium', 'high', 'critical', 'LOW', "MEDIUM", 'HIGH', voluptuous.Optional('description'): voluptuous.All(
'CRITICAL')), voluptuous.Any(str, unicode), voluptuous.Length(max=250)),
Optional('match_by'): All(Any([unicode], [str]), Length(max=255)), voluptuous.Optional('severity'): voluptuous.All(
Optional('ok_actions'): All(Any([str], [unicode]), Length(max=400)), voluptuous.Any('low', 'medium', 'high', 'critical', 'LOW', "MEDIUM",
Optional('alarm_actions'): All(Any([str], [unicode]), Length(max=400)), 'HIGH', 'CRITICAL')),
Optional('undetermined_actions'): All(Any([str], [unicode]), voluptuous.Optional('match_by'): voluptuous.All(
Length(max=400))} voluptuous.Any([unicode], [str]), voluptuous.Length(max=255)),
voluptuous.Optional('ok_actions'): voluptuous.All(
voluptuous.Any([str], [unicode]), voluptuous.Length(max=400)),
voluptuous.Optional('alarm_actions'): voluptuous.All(
voluptuous.Any([str], [unicode]), voluptuous.Length(max=400)),
voluptuous.Optional('undetermined_actions'): voluptuous.All(
voluptuous.Any([str], [unicode]), voluptuous.Length(max=400))}
request_body_schema = Schema(alarm_definition_schema, required=True, request_body_schema = voluptuous.Schema(alarm_definition_schema, required=True,
extra=True) extra=True)

View File

@ -12,15 +12,17 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from voluptuous import Schema import voluptuous
from voluptuous import Any, All, Length
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.v2.common.schemas import exceptions from monasca.v2.common.schemas import exceptions
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
dimensions_schema = Schema({All(Any(str, unicode), Length(max=255)): dimensions_schema = voluptuous.Schema({
All(Any(str, unicode), Length(max=255))}) voluptuous.All(voluptuous.Any(str, unicode),
voluptuous.Length(max=255)): voluptuous.All(
voluptuous.Any(str, unicode), voluptuous.Length(max=255))})
def validate(dimensions): def validate(dimensions):

View File

@ -12,17 +12,17 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from voluptuous import Schema import voluptuous
from voluptuous import Any, All, Length
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.v2.common.schemas import exceptions from monasca.v2.common.schemas import exceptions
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
# TODO: Add regex to validate key/values don't use any excluded characters. event_schema_request_body = voluptuous.Schema({
event_schema_request_body = Schema({All(Any(str, unicode), Length(max=255)): voluptuous.All(voluptuous.Any(str, unicode),
All(Any(None, str, unicode, bool, int, voluptuous.Length(max=255)): voluptuous.All(
float, dict, []))}) voluptuous.Any(None, str, unicode, bool, int, float, dict, []))})
def validate(body): def validate(body):

View File

@ -12,14 +12,15 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from voluptuous import Schema import voluptuous
from voluptuous import Any, All, Length
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.v2.common.schemas import exceptions from monasca.v2.common.schemas import exceptions
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
metric_name_schema = Schema(All(Any(str, unicode), Length(max=64))) metric_name_schema = voluptuous.Schema(
voluptuous.All(voluptuous.Any(str, unicode), voluptuous.Length(max=64)))
def validate(name): def validate(name):

View File

@ -12,23 +12,24 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from voluptuous import Schema import voluptuous
from voluptuous import Required, Any, All, Range, Optional
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.v2.common.schemas import metric_name_schema
from monasca.v2.common.schemas import dimensions_schema from monasca.v2.common.schemas import dimensions_schema
from monasca.v2.common.schemas import exceptions from monasca.v2.common.schemas import exceptions
from monasca.v2.common.schemas import metric_name_schema
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
metric_schema = { metric_schema = {
Required('name'): metric_name_schema.metric_name_schema, voluptuous.Required('name'): metric_name_schema.metric_name_schema,
Optional('dimensions'): dimensions_schema.dimensions_schema, voluptuous.Optional('dimensions'): dimensions_schema.dimensions_schema,
Required('timestamp'): All(Any(int, float), Range(min=0)), voluptuous.Required('timestamp'): voluptuous.All(
Required('value'): Any(int, float) voluptuous.Any(int, float), voluptuous.Range(min=0)),
} voluptuous.Required('value'): voluptuous.Any(int, float)}
request_body_schema = Schema(Any(metric_schema, [metric_schema])) request_body_schema = voluptuous.Schema(
voluptuous.Any(metric_schema, [metric_schema]))
def validate(msg): def validate(msg):

View File

@ -12,20 +12,24 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from voluptuous import Schema import voluptuous
from voluptuous import Optional, Required, Any, All, Length
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.v2.common.schemas import exceptions from monasca.v2.common.schemas import exceptions
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
notification_schema = { notification_schema = {
Required('name'): Schema(All(Any(str, unicode), Length(max=250))), voluptuous.Required('name'): voluptuous.Schema(
Required('type'): Schema(Any("EMAIL", "email")), voluptuous.All(voluptuous.Any(str, unicode),
Required('address'): Schema(All(Any(str, unicode), Length(max=100))) voluptuous.Length(max=250))),
} voluptuous.Required('type'): voluptuous.Schema(
voluptuous.Any("EMAIL", "email")),
voluptuous.Required('address'): voluptuous.Schema(
voluptuous.All(voluptuous.Any(str, unicode),
voluptuous.Length(max=100)))}
request_body_schema = Schema(Any(notification_schema)) request_body_schema = voluptuous.Schema(voluptuous.Any(notification_schema))
def validate(msg): def validate(msg):

View File

@ -12,22 +12,26 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from voluptuous import Schema import voluptuous
from voluptuous import Optional, Required, Any, All, Length
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.v2.common.schemas import exceptions from monasca.v2.common.schemas import exceptions
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
transform_schema = { transform_schema = {
Required('name'): Schema(All(Any(str, unicode), Length(max=64))), voluptuous.Required('name'): voluptuous.Schema(
Required('description'): Schema(All(Any(str, unicode), Length(max=250))), voluptuous.All(voluptuous.Any(str, unicode),
Required('specification'): voluptuous.Length(max=64))),
Schema(All(Any(str, unicode), Length(max=64536))), voluptuous.Required('description'): voluptuous.Schema(
Optional('enabled'): bool voluptuous.All(voluptuous.Any(str, unicode),
} voluptuous.Length(max=250))),
voluptuous.Required('specification'): voluptuous.Schema(
voluptuous.All(voluptuous.Any(str, unicode),
voluptuous.Length(max=64536))),
voluptuous.Optional('enabled'): bool}
request_body_schema = Schema(Any(transform_schema)) request_body_schema = voluptuous.Schema(voluptuous.Any(transform_schema))
def validate(msg): def validate(msg):

View File

@ -11,21 +11,24 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json import json
import re import re
from pyparsing import ParseException
import falcon import falcon
from oslo.config import cfg from oslo.config import cfg
import pyparsing
from monasca.api.alarm_definitions_api_v2 import AlarmDefinitionsV2API
from monasca.common.repositories import exceptions from monasca.common.repositories import exceptions
from monasca.common import resource_api from monasca.common import resource_api
from monasca.api.alarm_definitions_api_v2 import AlarmDefinitionsV2API import monasca.expression_parser.alarm_expr_parser
from monasca.expression_parser.alarm_expr_parser import AlarmExprParser
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.v2.reference import helpers from monasca.v2.common.schemas import (alarm_definition_request_body_schema
from monasca.v2.common.schemas import alarm_definition_request_body_schema as schema_alarms as schema_alarms)
from monasca.v2.common.schemas import exceptions as schemas_exceptions from monasca.v2.common.schemas import exceptions as schemas_exceptions
from monasca.v2.reference.alarming import Alarming from monasca.v2.reference.alarming import Alarming
from monasca.v2.reference import helpers
from monasca.v2.reference.helpers import read_json_msg_body from monasca.v2.reference.helpers import read_json_msg_body
from monasca.v2.reference.resource import resource_try_catch_block from monasca.v2.reference.resource import resource_try_catch_block
@ -43,13 +46,13 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming):
self._region = cfg.CONF.region self._region = cfg.CONF.region
self._default_authorized_roles = \ self._default_authorized_roles = (
cfg.CONF.security.default_authorized_roles cfg.CONF.security.default_authorized_roles)
self._delegate_authorized_roles = \ self._delegate_authorized_roles = (
cfg.CONF.security.delegate_authorized_roles cfg.CONF.security.delegate_authorized_roles)
self._post_metrics_authorized_roles = \ self._post_metrics_authorized_roles = (
cfg.CONF.security.default_authorized_roles + \ cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.agent_authorized_roles cfg.CONF.security.agent_authorized_roles)
self._alarm_definitions_repo = resource_api.init_driver( self._alarm_definitions_repo = resource_api.init_driver(
'monasca.repositories', 'monasca.repositories',
@ -134,9 +137,8 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming):
@resource_try_catch_block @resource_try_catch_block
def _alarm_definition_show(self, tenant_id, id): def _alarm_definition_show(self, tenant_id, id):
alarm_definition_row = \ alarm_definition_row = (
self._alarm_definitions_repo.get_alarm_definition( self._alarm_definitions_repo.get_alarm_definition(tenant_id, id))
tenant_id, id)
match_by = get_comma_separated_str_as_list( match_by = get_comma_separated_str_as_list(
alarm_definition_row.match_by) alarm_definition_row.match_by)
@ -168,9 +170,8 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming):
@resource_try_catch_block @resource_try_catch_block
def _alarm_definition_delete(self, tenant_id, id): def _alarm_definition_delete(self, tenant_id, id):
sub_alarm_definition_rows = \ sub_alarm_definition_rows = (
self._alarm_definitions_repo.get_sub_alarm_definitions( self._alarm_definitions_repo.get_sub_alarm_definitions(id))
id)
alarm_metric_rows = self._alarm_definitions_repo.get_alarm_metrics( alarm_metric_rows = self._alarm_definitions_repo.get_alarm_metrics(
tenant_id, id) tenant_id, id)
sub_alarm_rows = self._alarm_definitions_repo.get_sub_alarms( sub_alarm_rows = self._alarm_definitions_repo.get_sub_alarms(
@ -189,9 +190,9 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming):
@resource_try_catch_block @resource_try_catch_block
def _alarm_definition_list(self, tenant_id, name, dimensions, req_uri): def _alarm_definition_list(self, tenant_id, name, dimensions, req_uri):
alarm_definition_rows = \ alarm_definition_rows = (
self._alarm_definitions_repo.get_alarm_definitions( self._alarm_definitions_repo.get_alarm_definitions(tenant_id, name,
tenant_id, name, dimensions) dimensions))
result = [] result = []
for alarm_definition_row in alarm_definition_rows: for alarm_definition_row in alarm_definition_rows:
@ -227,7 +228,6 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming):
return result return result
def _validate_alarm_definition(self, alarm_definition): def _validate_alarm_definition(self, alarm_definition):
try: try:
@ -243,20 +243,29 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming):
ok_actions): ok_actions):
try: try:
sub_expr_list = AlarmExprParser(expression).sub_expr_list sub_expr_list = (
monasca.expression_parser.alarm_expr_parser.
AlarmExprParser(expression).sub_expr_list)
except ParseException as ex: except pyparsing.ParseException as ex:
LOG.exception(ex) LOG.exception(ex)
title = "Invalid alarm expression".encode('utf8') title = "Invalid alarm expression".encode('utf8')
msg = "parser failed on expression '{}' at column {}".format( msg = "parser failed on expression '{}' at column {}".format(
expression.encode('utf8'), str(ex.column).encode('utf')) expression.encode('utf8'), str(ex.column).encode('utf'))
raise falcon.HTTPBadRequest(title, msg) raise falcon.HTTPBadRequest(title, msg)
alarm_definition_id = \ alarm_definition_id = (
self._alarm_definitions_repo.create_alarm_definition( self._alarm_definitions_repo.
tenant_id, name, expression, sub_expr_list, description, create_alarm_definition(tenant_id,
severity, match_by, alarm_actions, undetermined_actions, name,
ok_actions) expression,
sub_expr_list,
description,
severity,
match_by,
alarm_actions,
undetermined_actions,
ok_actions))
self._send_alarm_definition_created_event(tenant_id, self._send_alarm_definition_created_event(tenant_id,
alarm_definition_id, alarm_definition_id,
@ -334,6 +343,7 @@ class AlarmDefinitions(AlarmDefinitionsV2API, Alarming):
self._send_event(alarm_definition_created_event_msg) self._send_event(alarm_definition_created_event_msg)
def get_query_alarm_definition_name(alarm_definition): def get_query_alarm_definition_name(alarm_definition):
try: try:
if 'name' in alarm_definition: if 'name' in alarm_definition:
@ -407,6 +417,7 @@ def get_query_ok_actions(alarm_definition):
else: else:
return [] return []
def get_comma_separated_str_as_list(comma_separated_str): def get_comma_separated_str_as_list(comma_separated_str):
if not comma_separated_str: if not comma_separated_str:

View File

@ -12,12 +12,14 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json import json
import falcon import falcon
from oslo.config import cfg from oslo.config import cfg
from monasca.common import resource_api
from monasca.expression_parser.alarm_expr_parser import AlarmExprParser
from monasca.openstack.common import log
from monasca.common.messaging import exceptions as message_queue_exceptions from monasca.common.messaging import exceptions as message_queue_exceptions
from monasca.common import resource_api
import monasca.expression_parser.alarm_expr_parser
from monasca.openstack.common import log
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -33,10 +35,9 @@ class Alarming(object):
super(Alarming, self).__init__() super(Alarming, self).__init__()
self._message_queue \ self._message_queue = (
= resource_api.init_driver('monasca.messaging', resource_api.init_driver('monasca.messaging',
cfg.CONF.messaging.driver, cfg.CONF.messaging.driver, (['events'])))
(['events']))
def _send_alarm_deleted_event(self, tenant_id, alarm_definition_id, def _send_alarm_deleted_event(self, tenant_id, alarm_definition_id,
alarm_metric_rows, sub_alarm_rows): alarm_metric_rows, sub_alarm_rows):
@ -52,13 +53,15 @@ class Alarming(object):
else: else:
sub_alarm_dict[sub_alarm_row.alarm_id] = [sub_alarm_row] sub_alarm_dict[sub_alarm_row.alarm_id] = [sub_alarm_row]
# Forward declaration.
alarm_deleted_event_msg = {}
prev_alarm_id = None prev_alarm_id = None
for alarm_metric_row in alarm_metric_rows: for alarm_metric_row in alarm_metric_rows:
if prev_alarm_id != alarm_metric_row.alarm_id: if prev_alarm_id != alarm_metric_row.alarm_id:
if prev_alarm_id is not None: if prev_alarm_id is not None:
sub_alarms_deleted_event_msg = \ sub_alarms_deleted_event_msg = (
self._build_sub_alarm_deleted_event_msg( self._build_sub_alarm_deleted_event_msg(sub_alarm_dict,
sub_alarm_dict, prev_alarm_id) prev_alarm_id))
alarm_deleted_event_msg[u'alarm-delete'][ alarm_deleted_event_msg[u'alarm-delete'][
u'subAlarms': sub_alarms_deleted_event_msg] u'subAlarms': sub_alarms_deleted_event_msg]
self._send_event(alarm_deleted_event_msg) self._send_event(alarm_deleted_event_msg)
@ -99,7 +102,8 @@ class Alarming(object):
for sub_alarm in sub_alarm_dict[alarm_id]: for sub_alarm in sub_alarm_dict[alarm_id]:
# There's only one expr in a sub alarm, so just take the first. # There's only one expr in a sub alarm, so just take the first.
sub_expr = AlarmExprParser(sub_alarm.expression).sub_expr_list[0] sub_expr = (monasca.expression_parser.alarm_expr_parser.
AlarmExprParser(sub_alarm.expression).sub_expr_list[0])
dimensions = {} dimensions = {}
sub_alarms_deleted_event_msg[sub_alarm.sub_alarm_id] = { sub_alarms_deleted_event_msg[sub_alarm.sub_alarm_id] = {
u'function': sub_expr.normalized_func, u'function': sub_expr.normalized_func,

View File

@ -11,17 +11,19 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json import json
from falcon.util.uri import parse_query_string
import re import re
import falcon import falcon
from oslo.config import cfg from oslo.config import cfg
from monasca.api.alarms_api_v2 import AlarmsV2API from monasca.api.alarms_api_v2 import AlarmsV2API
from monasca.common.repositories import exceptions from monasca.common.repositories import exceptions
from monasca.common import resource_api from monasca.common import resource_api
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.v2.reference import helpers
from monasca.v2.reference.alarming import Alarming from monasca.v2.reference.alarming import Alarming
from monasca.v2.reference import helpers
from monasca.v2.reference.resource import resource_try_catch_block from monasca.v2.reference.resource import resource_try_catch_block
@ -38,17 +40,20 @@ class Alarms(AlarmsV2API, Alarming):
self._region = cfg.CONF.region self._region = cfg.CONF.region
self._default_authorized_roles = \ self._default_authorized_roles = (
cfg.CONF.security.default_authorized_roles cfg.CONF.security.default_authorized_roles)
self._delegate_authorized_roles = \ self._delegate_authorized_roles = (
cfg.CONF.security.delegate_authorized_roles cfg.CONF.security.delegate_authorized_roles)
self._post_metrics_authorized_roles = \ self._post_metrics_authorized_roles = (
cfg.CONF.security.default_authorized_roles + \ cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.agent_authorized_roles cfg.CONF.security.agent_authorized_roles)
self._alarms_repo = resource_api.init_driver( self._alarms_repo = resource_api.init_driver(
'monasca.repositories', cfg.CONF.repositories.alarms_driver) 'monasca.repositories', cfg.CONF.repositories.alarms_driver)
self._metrics_repo = resource_api.init_driver(
'monasca.repositories', cfg.CONF.repositories.metrics_driver)
except Exception as ex: except Exception as ex:
LOG.exception(ex) LOG.exception(ex)
raise exceptions.RepositoryException(ex) raise exceptions.RepositoryException(ex)
@ -91,7 +96,7 @@ class Alarms(AlarmsV2API, Alarming):
helpers.validate_authorization(req, self._default_authorized_roles) helpers.validate_authorization(req, self._default_authorized_roles)
tenant_id = helpers.get_tenant_id(req) tenant_id = helpers.get_tenant_id(req)
query_parms = parse_query_string(req.query_string) query_parms = falcon.uri.parse_query_string(req.query_string)
result = self._alarm_list(req.uri, tenant_id, query_parms) result = self._alarm_list(req.uri, tenant_id, query_parms)
@ -101,6 +106,11 @@ class Alarms(AlarmsV2API, Alarming):
@resource_api.Restify('/v2.0/alarms/{id}', method='get') @resource_api.Restify('/v2.0/alarms/{id}', method='get')
def do_get_alarm_by_id(self, req, res, id): def do_get_alarm_by_id(self, req, res, id):
# Necessary because falcon interprets 'state-history' as an
# alarm id, and this url masks '/v2.0/alarms/state-history'.
if id.lower() == 'state-history':
return self.do_get_alarms_state_history(req, res)
helpers.validate_authorization(req, self._default_authorized_roles) helpers.validate_authorization(req, self._default_authorized_roles)
tenant_id = helpers.get_tenant_id(req) tenant_id = helpers.get_tenant_id(req)
@ -109,6 +119,53 @@ class Alarms(AlarmsV2API, Alarming):
res.body = json.dumps(result, ensure_ascii=False).encode('utf8') res.body = json.dumps(result, ensure_ascii=False).encode('utf8')
res.status = falcon.HTTP_200 res.status = falcon.HTTP_200
@resource_api.Restify('/v2.0/alarms/state-history', method='get')
def do_get_alarms_state_history(self, req, res):
helpers.validate_authorization(req, self._default_authorized_roles)
tenant_id = helpers.get_tenant_id(req)
start_timestamp = helpers.get_query_starttime_timestamp(req, False)
end_timestamp = helpers.get_query_endtime_timestamp(req, False)
query_parms = falcon.uri.parse_query_string(req.query_string)
result = self._alarm_history_list(tenant_id, start_timestamp,
end_timestamp, query_parms)
res.body = json.dumps(result, ensure_ascii=False).encode('utf8')
res.status = falcon.HTTP_200
@resource_api.Restify('/v2.0/alarms/{id}/state-history', method='get')
def do_get_alarm_state_history(self, req, res, id):
helpers.validate_authorization(req, self._default_authorized_roles)
tenant_id = helpers.get_tenant_id(req)
result = self._alarm_history(tenant_id, [id])
res.body = json.dumps(result, ensure_ascii=False).encode('utf8')
res.status = falcon.HTTP_200
@resource_try_catch_block
def _alarm_history_list(self, tenant_id, start_timestamp,
end_timestamp, query_parms):
# get_alarms expects 'metric_dimensions' for dimensions key.
if 'dimensions' in query_parms:
new_query_parms = {'metric_dimensions': query_parms['dimensions']}
else:
new_query_parms = {}
alarm_rows = self._alarms_repo.get_alarms(tenant_id, new_query_parms)
alarm_id_list = [alarm_row.alarm_id for alarm_row in alarm_rows]
return self._metrics_repo.alarm_history(tenant_id, alarm_id_list,
start_timestamp, end_timestamp)
@resource_try_catch_block
def _alarm_history(self, tenant_id, alarm_id):
return self._metrics_repo.alarm_history(tenant_id, alarm_id)
@resource_try_catch_block @resource_try_catch_block
def _alarm_delete(self, tenant_id, id): def _alarm_delete(self, tenant_id, id):
@ -171,6 +228,8 @@ class Alarms(AlarmsV2API, Alarming):
if not alarm_rows: if not alarm_rows:
return result return result
# Forward declaration
alarm = {}
prev_alarm_id = None prev_alarm_id = None
for alarm_row in alarm_rows: for alarm_row in alarm_rows:
if prev_alarm_id != alarm_row.alarm_id: if prev_alarm_id != alarm_row.alarm_id:

View File

@ -17,37 +17,40 @@ import json
import falcon import falcon
from oslo.config import cfg from oslo.config import cfg
from monasca.openstack.common import log
from monasca.api import monasca_events_api_v2 from monasca.api import monasca_events_api_v2
from monasca.common import resource_api
from monasca.common.messaging import exceptions as message_queue_exceptions from monasca.common.messaging import exceptions as message_queue_exceptions
from monasca.common.messaging.message_formats import events_transform_factory from monasca.common.messaging.message_formats import events_transform_factory
from monasca.v2.common import utils from monasca.common import resource_api
from monasca.openstack.common import log
from monasca.v2.common.schemas import (
events_request_body_schema as schemas_event)
from monasca.v2.common.schemas import exceptions as schemas_exceptions from monasca.v2.common.schemas import exceptions as schemas_exceptions
from monasca.v2.common.schemas import \ from monasca.v2.common import utils
events_request_body_schema as schemas_event
from monasca.v2.reference import helpers from monasca.v2.reference import helpers
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class Events(monasca_events_api_v2.EventsV2API): class Events(monasca_events_api_v2.EventsV2API):
def __init__(self, global_conf): def __init__(self, global_conf):
super(Events, self).__init__(global_conf) super(Events, self).__init__(global_conf)
self._region = cfg.CONF.region self._region = cfg.CONF.region
self._default_authorized_roles = \ self._default_authorized_roles = (
cfg.CONF.security.default_authorized_roles cfg.CONF.security.default_authorized_roles)
self._delegate_authorized_roles = \ self._delegate_authorized_roles = (
cfg.CONF.security.delegate_authorized_roles cfg.CONF.security.delegate_authorized_roles)
self._post_events_authorized_roles = \ self._post_events_authorized_roles = (
cfg.CONF.security.default_authorized_roles + \ cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.agent_authorized_roles cfg.CONF.security.agent_authorized_roles)
self._event_transform = \ self._event_transform = (
events_transform_factory.create_events_transform() events_transform_factory.create_events_transform())
self._message_queue = \ self._message_queue = (
resource_api.init_driver('monasca.messaging', resource_api.init_driver('monasca.messaging',
cfg.CONF.messaging.driver, cfg.CONF.messaging.driver,
['raw-events']) ['raw-events']))
def _validate_event(self, event): def _validate_event(self, event):
"""Validates the event """Validates the event

View File

@ -11,25 +11,25 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import datetime import datetime
import json import json
from urlparse import urlparse import urlparse
import falcon import falcon
from falcon.util.uri import parse_query_string import simplejson
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.v2.common.schemas import dimensions_schema
from monasca.v2.common.schemas import exceptions as schemas_exceptions from monasca.v2.common.schemas import exceptions as schemas_exceptions
from monasca.v2.common.schemas import metric_name_schema from monasca.v2.common.schemas import metric_name_schema
from monasca.v2.common.schemas import dimensions_schema
import simplejson
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
def read_json_msg_body(req): def read_json_msg_body(req):
""" """Read the json_msg from the http request body and return them as JSON.
Read the json_msg from the http request body and return them as JSON.
:param req: HTTP request object. :param req: HTTP request object.
:return: Returns the metrics as a JSON object. :return: Returns the metrics as a JSON object.
:raises falcon.HTTPBadRequest: :raises falcon.HTTPBadRequest:
@ -51,8 +51,7 @@ def validate_json_content_type(req):
def is_in_role(req, authorized_roles): def is_in_role(req, authorized_roles):
"""Determines if one or more of the X-ROLES is in the supplied """Is one or more of the X-ROLES in the supplied authorized_roles.
authorized_roles.
:param req: HTTP request object. Must contain "X-ROLES" in the HTTP :param req: HTTP request object. Must contain "X-ROLES" in the HTTP
request header. request header.
@ -107,7 +106,7 @@ def get_x_tenant_or_tenant_id(req, delegate_authorized_roles):
:returns: Returns the cross tenant or tenant ID. :returns: Returns the cross tenant or tenant ID.
""" """
if is_in_role(req, delegate_authorized_roles): if is_in_role(req, delegate_authorized_roles):
params = parse_query_string(req.query_string) params = falcon.uri.parse_query_string(req.query_string)
if 'tenant_id' in params: if 'tenant_id' in params:
tenant_id = params['tenant_id'] tenant_id = params['tenant_id']
return tenant_id return tenant_id
@ -115,12 +114,12 @@ def get_x_tenant_or_tenant_id(req, delegate_authorized_roles):
def get_query_name(req, name_required=False): def get_query_name(req, name_required=False):
""" """Returns the query param "name" if supplied.
Returns the query param "name" if supplied.
:param req: HTTP request object. :param req: HTTP request object.
""" """
try: try:
params = parse_query_string(req.query_string) params = falcon.uri.parse_query_string(req.query_string)
if 'name' in params: if 'name' in params:
name = params['name'] name = params['name']
return name return name
@ -142,7 +141,7 @@ def get_query_dimensions(req):
:raises falcon.HTTPBadRequest: If dimensions are malformed. :raises falcon.HTTPBadRequest: If dimensions are malformed.
""" """
try: try:
params = parse_query_string(req.query_string) params = falcon.uri.parse_query_string(req.query_string)
dimensions = {} dimensions = {}
if 'dimensions' in params: if 'dimensions' in params:
dimensions_str = params['dimensions'] dimensions_str = params['dimensions']
@ -160,23 +159,29 @@ def get_query_dimensions(req):
raise falcon.HTTPBadRequest('Bad request', ex.message) raise falcon.HTTPBadRequest('Bad request', ex.message)
def get_query_starttime_timestamp(req): def get_query_starttime_timestamp(req, required=True):
try: try:
params = parse_query_string(req.query_string) params = falcon.uri.parse_query_string(req.query_string)
if 'start_time' in params: if 'start_time' in params:
return _convert_time_string(params['start_time']) return _convert_time_string(params['start_time'])
else: else:
if required:
raise Exception("Missing start time") raise Exception("Missing start time")
else:
return None
except Exception as ex: except Exception as ex:
LOG.debug(ex) LOG.debug(ex)
raise falcon.HTTPBadRequest('Bad request', ex.message) raise falcon.HTTPBadRequest('Bad request', ex.message)
def get_query_endtime_timestamp(req): def get_query_endtime_timestamp(req, required=True):
try: try:
params = parse_query_string(req.query_string) params = falcon.uri.parse_query_string(req.query_string)
if 'end_time' in params: if 'end_time' in params:
return _convert_time_string(params['end_time']) return _convert_time_string(params['end_time'])
else:
if required:
raise Exception("Missing end time")
else: else:
return None return None
except Exception as ex: except Exception as ex:
@ -192,7 +197,7 @@ def _convert_time_string(date_time_string):
def get_query_statistics(req): def get_query_statistics(req):
try: try:
params = parse_query_string(req.query_string) params = falcon.uri.parse_query_string(req.query_string)
if 'statistics' in params: if 'statistics' in params:
statistics = params['statistics'].split(',') statistics = params['statistics'].split(',')
statistics = [statistic.lower() for statistic in statistics] statistics = [statistic.lower() for statistic in statistics]
@ -209,7 +214,7 @@ def get_query_statistics(req):
def get_query_period(req): def get_query_period(req):
try: try:
params = parse_query_string(req.query_string) params = falcon.uri.parse_query_string(req.query_string)
if 'period' in params: if 'period' in params:
return params['period'] return params['period']
else: else:

View File

@ -16,22 +16,17 @@ import json
import falcon import falcon
from oslo.config import cfg from oslo.config import cfg
from stevedore import driver
from monasca.openstack.common import log
from monasca.api import monasca_api_v2 from monasca.api import monasca_api_v2
from monasca.common import resource_api
from monasca.common.messaging import exceptions as message_queue_exceptions from monasca.common.messaging import exceptions as message_queue_exceptions
from monasca.common.messaging.message_formats import metrics_transform_factory from monasca.common.messaging.message_formats import metrics_transform_factory
from monasca.common import resource_api
from monasca.openstack.common import log
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.v2.common import utils 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 import helpers
from monasca.v2.reference.helpers import read_json_msg_body
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -41,21 +36,23 @@ class Metrics(monasca_api_v2.V2API):
def __init__(self, global_conf): def __init__(self, global_conf):
try: try:
super(Metrics, self).__init__(global_conf) super(Metrics, self).__init__(global_conf)
self._region = cfg.CONF.region self._region = cfg.CONF.region
self._default_authorized_roles = \ self._default_authorized_roles = (
cfg.CONF.security.default_authorized_roles cfg.CONF.security.default_authorized_roles)
self._delegate_authorized_roles = \ self._delegate_authorized_roles = (
cfg.CONF.security.delegate_authorized_roles cfg.CONF.security.delegate_authorized_roles)
self._post_metrics_authorized_roles = \ self._post_metrics_authorized_roles = (
cfg.CONF.security.default_authorized_roles + \ cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.agent_authorized_roles cfg.CONF.security.agent_authorized_roles)
self._metrics_transform = \ self._metrics_transform = (
metrics_transform_factory.create_metrics_transform() metrics_transform_factory.create_metrics_transform())
self._message_queue = resource_api.init_driver( self._message_queue = (
'monasca.messaging', resource_api.init_driver('monasca.messaging',
cfg.CONF.messaging.driver, cfg.CONF.messaging.driver,
['metrics']) ['metrics']))
self._metrics_repo = resource_api.init_driver( self._metrics_repo = resource_api.init_driver(
'monasca.repositories', cfg.CONF.repositories.metrics_driver) 'monasca.repositories', cfg.CONF.repositories.metrics_driver)
@ -90,7 +87,7 @@ class Metrics(monasca_api_v2.V2API):
except message_queue_exceptions.MessageQueueException as ex: except message_queue_exceptions.MessageQueueException as ex:
LOG.exception(ex) LOG.exception(ex)
raise falcon.HTTPServiceUnavailable('Service unavailable', raise falcon.HTTPServiceUnavailable('Service unavailable',
ex.message) ex.message, 60)
if isinstance(metrics, list): if isinstance(metrics, list):
for metric in metrics: for metric in metrics:
@ -112,7 +109,7 @@ class Metrics(monasca_api_v2.V2API):
except Exception as ex: except Exception as ex:
LOG.exception(ex) LOG.exception(ex)
raise falcon.HTTPServiceUnavailable('Service unavailable', raise falcon.HTTPServiceUnavailable('Service unavailable',
ex.message) ex.message, 60)
def _measurement_list(self, tenant_id, name, dimensions, start_timestamp, def _measurement_list(self, tenant_id, name, dimensions, start_timestamp,
end_timestamp): end_timestamp):
@ -124,7 +121,7 @@ class Metrics(monasca_api_v2.V2API):
except Exception as ex: except Exception as ex:
LOG.exception(ex) LOG.exception(ex)
raise falcon.HTTPServiceUnavailable('Service unavailable', raise falcon.HTTPServiceUnavailable('Service unavailable',
ex.message) ex.message, 60)
def _metric_statistics(self, tenant_id, name, dimensions, start_timestamp, def _metric_statistics(self, tenant_id, name, dimensions, start_timestamp,
end_timestamp, statistics, period): end_timestamp, statistics, period):
@ -137,7 +134,7 @@ class Metrics(monasca_api_v2.V2API):
except Exception as ex: except Exception as ex:
LOG.exception(ex) LOG.exception(ex)
raise falcon.HTTPServiceUnavailable('Service unavailable', raise falcon.HTTPServiceUnavailable('Service unavailable',
ex.message) ex.message, 60)
@resource_api.Restify('/v2.0/metrics/', method='post') @resource_api.Restify('/v2.0/metrics/', method='post')
def do_post_metrics(self, req, res): def do_post_metrics(self, req, res):
@ -146,9 +143,9 @@ class Metrics(monasca_api_v2.V2API):
self._post_metrics_authorized_roles) self._post_metrics_authorized_roles)
metrics = helpers.read_http_resource(req) metrics = helpers.read_http_resource(req)
self._validate_metrics(metrics) self._validate_metrics(metrics)
tenant_id = \ tenant_id = (
helpers.get_x_tenant_or_tenant_id(req, helpers.get_x_tenant_or_tenant_id(req,
self._delegate_authorized_roles) self._delegate_authorized_roles))
transformed_metrics = self._metrics_transform(metrics, tenant_id, transformed_metrics = self._metrics_transform(metrics, tenant_id,
self._region) self._region)
self._send_metrics(transformed_metrics) self._send_metrics(transformed_metrics)
@ -175,7 +172,7 @@ class Metrics(monasca_api_v2.V2API):
dimensions = helpers.get_query_dimensions(req) dimensions = helpers.get_query_dimensions(req)
helpers.validate_query_dimensions(dimensions) helpers.validate_query_dimensions(dimensions)
start_timestamp = helpers.get_query_starttime_timestamp(req) start_timestamp = helpers.get_query_starttime_timestamp(req)
end_timestamp = helpers.get_query_endtime_timestamp(req) end_timestamp = helpers.get_query_endtime_timestamp(req, False)
result = self._measurement_list(tenant_id, name, dimensions, result = self._measurement_list(tenant_id, name, dimensions,
start_timestamp, end_timestamp) start_timestamp, end_timestamp)
res.body = json.dumps(result, ensure_ascii=False).encode('utf8') res.body = json.dumps(result, ensure_ascii=False).encode('utf8')
@ -190,7 +187,7 @@ class Metrics(monasca_api_v2.V2API):
dimensions = helpers.get_query_dimensions(req) dimensions = helpers.get_query_dimensions(req)
helpers.validate_query_dimensions(dimensions) helpers.validate_query_dimensions(dimensions)
start_timestamp = helpers.get_query_starttime_timestamp(req) start_timestamp = helpers.get_query_starttime_timestamp(req)
end_timestamp = helpers.get_query_endtime_timestamp(req) end_timestamp = helpers.get_query_endtime_timestamp(req, False)
statistics = helpers.get_query_statistics(req) statistics = helpers.get_query_statistics(req)
period = helpers.get_query_period(req) period = helpers.get_query_period(req)
result = self._metric_statistics(tenant_id, name, dimensions, result = self._metric_statistics(tenant_id, name, dimensions,

View File

@ -12,22 +12,19 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# TODO: Used simplejson to read the yaml as simplejson transforms to "str"
# not "unicode"
import json import json
import falcon import falcon
from oslo.config import cfg from oslo.config import cfg
from monasca.api import monasca_notifications_api_v2
from monasca.common.repositories import exceptions as repository_exceptions
from monasca.common import resource_api
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.openstack.common import uuidutils from monasca.openstack.common import uuidutils
from monasca.api import monasca_notifications_api_v2 from monasca.v2.common.schemas import (exceptions as schemas_exceptions)
from monasca.common import resource_api from monasca.v2.common.schemas import (
from monasca.common.repositories import exceptions as repository_exceptions notifications_request_body_schema as schemas_notifications)
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.reference import helpers from monasca.v2.reference import helpers
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -37,8 +34,8 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API):
def __init__(self, global_conf): def __init__(self, global_conf):
super(Notifications, self).__init__(global_conf) super(Notifications, self).__init__(global_conf)
self._region = cfg.CONF.region self._region = cfg.CONF.region
self._default_authorized_roles = \ self._default_authorized_roles = (
cfg.CONF.security.default_authorized_roles cfg.CONF.security.default_authorized_roles)
self._notifications_repo = resource_api.init_driver( self._notifications_repo = resource_api.init_driver(
'monasca.repositories', cfg.CONF.repositories.notifications_driver) 'monasca.repositories', cfg.CONF.repositories.notifications_driver)
@ -66,7 +63,8 @@ class Notifications(monasca_notifications_api_v2.NotificationsV2API):
address = notification['address'] address = notification['address']
if self._notifications_repo.exists(tenant_id, name): if self._notifications_repo.exists(tenant_id, name):
raise falcon.HTTPConflict('Conflict', ( raise falcon.HTTPConflict('Conflict', (
'Notification Method already exists: tenant_id=%s name=%s' % ( 'Notification Method already exists: tenant_id=%s '
'name=%s' % (
tenant_id, name)), code=409) tenant_id, name)), code=409)
self._notifications_repo.create_notification(id, tenant_id, name, self._notifications_repo.create_notification(id, tenant_id, name,
notification_type, notification_type,

View File

@ -1,6 +1,19 @@
# 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 falcon import falcon
from monasca.common.messaging import exceptions
from monasca.common.repositories.exceptions import DoesNotExistException from monasca.common.repositories import exceptions
from monasca.openstack.common import log from monasca.openstack.common import log
@ -17,7 +30,7 @@ def resource_try_catch_block(fun):
except falcon.HTTPNotFound: except falcon.HTTPNotFound:
raise raise
except DoesNotExistException: except exceptions.DoesNotExistException:
raise falcon.HTTPNotFound raise falcon.HTTPNotFound
except falcon.HTTPBadRequest: except falcon.HTTPBadRequest:
raise raise

View File

@ -12,34 +12,32 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# TODO: Used simplejson to read the yaml as simplejson transforms to "str"
# not "unicode"
import json import json
import simplejson
import falcon import falcon
from oslo.config import cfg from oslo.config import cfg
from monasca.api import monasca_transforms_api_v2
from monasca.common.repositories import exceptions as repository_exceptions
from monasca.common import resource_api
from monasca.openstack.common import log from monasca.openstack.common import log
from monasca.openstack.common import uuidutils from monasca.openstack.common import uuidutils
from monasca.api import monasca_transforms_api_v2 from monasca.v2.common.schemas import (exceptions as schemas_exceptions)
from monasca.common import resource_api from monasca.v2.common.schemas import (
from monasca.common.repositories import exceptions as repository_exceptions transforms_request_body_schema as schemas_transforms)
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.reference import helpers from monasca.v2.reference import helpers
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class Transforms(monasca_transforms_api_v2.TransformsV2API): class Transforms(monasca_transforms_api_v2.TransformsV2API):
def __init__(self, global_conf): def __init__(self, global_conf):
super(Transforms, self).__init__(global_conf) super(Transforms, self).__init__(global_conf)
self._region = cfg.CONF.region self._region = cfg.CONF.region
self._default_authorized_roles = \ self._default_authorized_roles = (
cfg.CONF.security.default_authorized_roles cfg.CONF.security.default_authorized_roles)
self._transforms_repo = resource_api.init_driver( self._transforms_repo = resource_api.init_driver(
'monasca.repositories', cfg.CONF.repositories.transforms_driver) 'monasca.repositories', cfg.CONF.repositories.transforms_driver)

View File

@ -8,4 +8,4 @@ gunicorn>=19.1.0
oslo.config>=1.2.1 oslo.config>=1.2.1
ujson>=1.33 ujson>=1.33
Pyparsing>=2.0.3 Pyparsing>=2.0.3
pyodbc>=3.0.7 influxdb>=0.1.12

View File

@ -9,6 +9,7 @@ pep8<=1.5.6
httplib2>=0.7.5 httplib2>=0.7.5
mock>=1.0 mock>=1.0
mox>=0.5.3 mox>=0.5.3
nose
# Docs Requirements # Docs Requirements
oslosphinx oslosphinx
oslotest oslotest

12
tox.ini
View File

@ -4,14 +4,12 @@ skipsdist = True
envlist = py27,py33,pep8 envlist = py27,py33,pep8
[testenv] [testenv]
setenv = VIRTUAL_ENV={envdir}
usedevelop = True
install_command = pip install -U {opts} {packages}
deps = -r{toxinidir}/requirements.txt deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt -r{toxinidir}/test-requirements.txt
install_command = pip install -U --allow-external pytidylib --allow-insecure pytidylib --allow-external netifaces --allow-insecure netifaces {opts} {packages} commands = nosetests
usedevelop = True
setenv = VIRTUAL_ENV={envdir}
commands = python setup.py testr --slowest --testr-args='--concurrency 1 {posargs}'
downloadcache = {toxworkdir}/_download
whitelist_externals = bash
[testenv:cover] [testenv:cover]
setenv = NOSE_WITH_COVERAGE=1 setenv = NOSE_WITH_COVERAGE=1
@ -32,8 +30,6 @@ commands = python setup.py build_sphinx
commands = {posargs} commands = {posargs}
[flake8] [flake8]
# H305 imports not grouped correctly
ignore = H305
builtins = _ builtins = _
exclude=.venv,.git,.tox,dist,doc,./monasca/openstack/common,*lib/python*,*egg,tools,build exclude=.venv,.git,.tox,dist,doc,./monasca/openstack/common,*lib/python*,*egg,tools,build
show-source = True show-source = True