293 lines
10 KiB
Python
Executable File
293 lines
10 KiB
Python
Executable File
# Copyright 2015 Carnegie Mellon University
|
|
#
|
|
# 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 ast
|
|
import falcon
|
|
import json
|
|
from oslo_config import cfg
|
|
import re
|
|
from stevedore import driver
|
|
import uuid
|
|
|
|
from kiloeyes.common import es_conn
|
|
from kiloeyes.common import namespace
|
|
from kiloeyes.common import resource_api
|
|
from oslo_log import log
|
|
|
|
|
|
NOTIFICATION_METHOD_OPTS = [
|
|
cfg.StrOpt('doc_type', default='notificationmethods',
|
|
help='The doc type that notification_methods '
|
|
'will be saved into.'),
|
|
cfg.StrOpt('index_strategy', default='fixed',
|
|
help='The index strategy used to create index name.'),
|
|
cfg.StrOpt('index_prefix', default='data_',
|
|
help='The index prefix where metrics were saved to.'),
|
|
cfg.IntOpt('size', default=10000,
|
|
help=('The query result limit. Any result set more than '
|
|
'the limit will be discarded. To see all the matching '
|
|
'result, narrow your search by using a small time '
|
|
'window or strong matching name')),
|
|
]
|
|
|
|
cfg.CONF.register_opts(NOTIFICATION_METHOD_OPTS, group="notificationmethods")
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class ParamUtil(object):
|
|
|
|
@staticmethod
|
|
def validateEmail(addr):
|
|
if len(addr) > 7:
|
|
if (re.match("^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\."
|
|
"([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$", addr)
|
|
is not None):
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def name(req):
|
|
# parse name from request
|
|
name = req.get_param('name')
|
|
|
|
if name and name.strip():
|
|
return name
|
|
else:
|
|
return "DefaultNotificationMethods"
|
|
|
|
@staticmethod
|
|
def type_address(req):
|
|
# parse notification type from request
|
|
# Default is EMAIL
|
|
type = req.get_param('type')
|
|
address = req.get_param('address')
|
|
|
|
# Currently, notification method types of email,
|
|
# PagerDuty and webhooks are supported.
|
|
# In the case of email, the address is the email address.
|
|
# For PagerDuty, the address is the PagerDuty Service API Key.
|
|
# For webhook, the address is the URL of the webhook.
|
|
if (type and type.strip() == 'EMAIL'
|
|
and address and address.strip()
|
|
and ParamUtil.validateEmail(address.strip())):
|
|
return ("EMAIL", address.strip())
|
|
elif (type and type.strip() == 'PAGEDUTY'
|
|
and address and address.strip()):
|
|
return ("PAGEDUTY", address.strip())
|
|
elif (type and type.strip() == 'WEBHOOK'
|
|
and address and address.strip()):
|
|
return ("WEBHOOK", address.strip())
|
|
else:
|
|
return None
|
|
|
|
|
|
class NotificationMethodDispatcher(object):
|
|
|
|
def __init__(self, global_conf):
|
|
LOG.debug('initializing V2API in NotificationMethodDispatcher!')
|
|
super(NotificationMethodDispatcher, self).__init__()
|
|
self.doc_type = cfg.CONF.notificationmethods.doc_type
|
|
self.size = cfg.CONF.notificationmethods.size
|
|
|
|
# load index strategy
|
|
if cfg.CONF.notificationmethods.index_strategy:
|
|
self.index_strategy = driver.DriverManager(
|
|
namespace.STRATEGY_NS,
|
|
cfg.CONF.notificationmethods.index_strategy,
|
|
invoke_on_load=True,
|
|
invoke_kwds={}).driver
|
|
LOG.debug(dir(self.index_strategy))
|
|
else:
|
|
self.index_strategy = None
|
|
|
|
self.index_prefix = cfg.CONF.notificationmethods.index_prefix
|
|
|
|
self._es_conn = es_conn.ESConnection(
|
|
self.doc_type, self.index_strategy, self.index_prefix)
|
|
|
|
def post_data(self, req, res):
|
|
LOG.debug('In NotificationMethodDispatcher::post_data.')
|
|
msg = req.stream.read()
|
|
# convert msg to dict
|
|
dict_msg = ast.literal_eval(msg)
|
|
|
|
# random uuid used for store the methods in database
|
|
id = str(uuid.uuid4())
|
|
|
|
# add an id to store in elasticsearch
|
|
dict_msg["id"] = id
|
|
|
|
# add an item "request" in the msg to tell
|
|
# the receiver this is a POST request
|
|
# The final msg is something like:
|
|
# {"id":"c60ec47e-5038-4bf1-9f95-4046c6e9a759",
|
|
# "request":"POST",
|
|
# "name":"TheName",
|
|
# "type":"TheType",
|
|
# "Address":"TheAddress"}
|
|
dict_msg["request"] = "POST"
|
|
|
|
LOG.debug("post notification method: %s" % dict_msg)
|
|
code = self.handle_notification_msg(dict_msg)
|
|
res.status = getattr(falcon, 'HTTP_' + str(code))
|
|
|
|
def put_data(self, req, res, id):
|
|
LOG.debug('In NotificationMethodDispatcher::put_data.')
|
|
msg = req.stream.read()
|
|
|
|
dict_msg = ast.literal_eval(msg)
|
|
|
|
# specify the id to match in elasticsearch for update
|
|
dict_msg["id"] = id
|
|
|
|
# add an item "request" in the msg to tell the receiver this is a PUT
|
|
# request
|
|
dict_msg["request"] = "PUT"
|
|
|
|
LOG.debug("put notification method: %s" % dict_msg)
|
|
code = self.handle_notification_msg(dict_msg)
|
|
res.status = getattr(falcon, 'HTTP_' + str(code))
|
|
|
|
def del_data(self, req, res, id):
|
|
LOG.debug('In NotificationMethodDispatcher::del_data.')
|
|
|
|
dict_msg = {}
|
|
|
|
# specify the id to match in elasticsearch for deletion
|
|
dict_msg["id"] = id
|
|
|
|
# add an item "request" in the msg to tell the receiver this is a DEL
|
|
# request
|
|
dict_msg["request"] = "DEL"
|
|
|
|
LOG.debug("delete notification method: %s" % dict_msg)
|
|
code = self.handle_notification_msg(dict_msg)
|
|
res.status = getattr(falcon, 'HTTP_' + str(code))
|
|
|
|
def _get_notification_method_response(self, res):
|
|
if res and res.status_code == 200:
|
|
obj = res.json()
|
|
if obj:
|
|
return obj.get('hits')
|
|
return None
|
|
else:
|
|
return None
|
|
|
|
def handle_notification_msg(self, dict_msg):
|
|
# dict_msg's format is:
|
|
# {"id":"c60ec47e-5038-4bf1-9f95-4046c6e9a759",
|
|
# "request":"POST",
|
|
# "name":"TheName",
|
|
# "type":"TheType",
|
|
# "Address":"TheAddress"}
|
|
# We add the POS/PUT/DEL in the message to indicate the request
|
|
# type
|
|
|
|
# Get the notification id from the message,
|
|
# this id will be used as _id for elasticsearch,
|
|
# and also stored as id in the notification_methods document
|
|
# type
|
|
|
|
# convert to dict, pop request, and get id
|
|
# after request is removed, the dict can be converted to
|
|
# request body for elasticsearch
|
|
request_type = dict_msg.pop("request", None)
|
|
id = dict_msg["id"]
|
|
|
|
if request_type is not None and id is not None:
|
|
# post
|
|
if request_type == 'POST':
|
|
return self._es_conn.post_messages(json.dumps(dict_msg), id)
|
|
|
|
# put
|
|
if request_type == 'PUT':
|
|
return self._es_conn.put_messages(json.dumps(dict_msg), id)
|
|
|
|
# delete
|
|
if request_type == 'DEL':
|
|
return self._es_conn.del_messages(id)
|
|
|
|
@resource_api.Restify('/v2.0/notification-methods/', method='get')
|
|
def do_get_notification_methods(self, req, res):
|
|
LOG.debug("The notification_methods GET request is received!")
|
|
|
|
es_res = self._es_conn.get_messages({})
|
|
res.status = getattr(falcon, 'HTTP_%s' % es_res.status_code)
|
|
|
|
LOG.debug('Query to ElasticSearch returned: %s' % es_res.status_code)
|
|
|
|
es_res = self._get_notification_method_response(es_res)
|
|
LOG.debug('Query to ElasticSearch returned: %s' % es_res)
|
|
|
|
res_data = es_res["hits"]
|
|
if res_data:
|
|
def _make_body(elements):
|
|
yield '{"links": [{"rel": "self", "href":"'
|
|
yield req.uri + '"}],'
|
|
yield '"elements": ['
|
|
first = True
|
|
for element in elements:
|
|
if element['_source']:
|
|
if not first:
|
|
yield ','
|
|
else:
|
|
first = False
|
|
links = [{"rel": "self",
|
|
"href": req.uri + "/" +
|
|
element['_source']['id']}]
|
|
element['_source']['links'] = links
|
|
yield json.dumps(element['_source'])
|
|
yield ']}'
|
|
|
|
res.body = ''.join(_make_body(res_data))
|
|
else:
|
|
res.body = ''
|
|
res.content_type = 'application/json;charset=utf-8'
|
|
|
|
@resource_api.Restify('/v2.0/notification-methods/{id}', method='get')
|
|
def do_get_notification_method_by_id(self, req, res, id):
|
|
LOG.debug("The notification_methods GET by id request is received!")
|
|
|
|
es_res = self._es_conn.get_message_by_id(id)
|
|
res.status = getattr(falcon, 'HTTP_%s' % es_res.status_code)
|
|
|
|
LOG.debug('Query to ElasticSearch returned: %s' % es_res.status_code)
|
|
|
|
es_res = self._get_notification_method_response(es_res)
|
|
LOG.debug('Query to ElasticSearch returned: %s' % es_res)
|
|
|
|
if es_res and es_res.get('hits'):
|
|
res_data = es_res['hits'][0]
|
|
obj = res_data['_source']
|
|
obj['id'] = id
|
|
obj['links'] = [{"rel": "self",
|
|
"href": req.uri}]
|
|
res.body = json.dumps(obj)
|
|
res.content_type = 'application/json;charset=utf-8'
|
|
else:
|
|
res.body = ''
|
|
|
|
@resource_api.Restify('/v2.0/notification-methods/', method='post')
|
|
def do_post_notification_methods(self, req, res):
|
|
self.post_data(req, res)
|
|
|
|
@resource_api.Restify('/v2.0/notification-methods/{id}', method='put')
|
|
def do_put_notification_methods(self, req, res, id):
|
|
self.put_data(req, res, id)
|
|
|
|
@resource_api.Restify('/v2.0/notification-methods/{id}', method='delete')
|
|
def do_delete_notification_methods(self, req, res, id):
|
|
self.del_data(req, res, id)
|