1203b762fa
Replace the following items with Python 3 style code. - six.moves.BaseHttpServer - six.moves.zip - six.PY2 - six.viewitems Change-Id: If68cc2778bfccc56248ab8fba3de764044a707e9
355 lines
12 KiB
Python
355 lines
12 KiB
Python
# Copyright 2017 - Nokia
|
|
#
|
|
# 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 http.server as BaseHTTPServer
|
|
from oslo_log import log as logging
|
|
import requests
|
|
import socket
|
|
from testtools import matchers
|
|
from threading import Thread
|
|
import time
|
|
|
|
from vitrage_tempest_plugin.tests.base import IsEmpty
|
|
from vitrage_tempest_plugin.tests.common.constants import VertexProperties as \
|
|
VProps
|
|
from vitrage_tempest_plugin.tests.common import vitrage_utils as v_utils
|
|
|
|
from vitrage_tempest_plugin.tests.e2e.test_actions_base import TestActionsBase
|
|
from vitrage_tempest_plugin.tests import utils
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
TRIGGER_ALARM_1 = 'e2e.test_webhook.alarm1'
|
|
TRIGGER_ALARM_2 = 'e2e.test_webhook.alarm2'
|
|
TRIGGER_ALARM_WITH_DEDUCED = 'e2e.test_webhook.alarm_with_deduced'
|
|
URL = 'url'
|
|
REGEX_FILTER = 'regex_filter'
|
|
HEADERS = 'headers'
|
|
HEADERS_PROPS = '{"content": "application/json"}'
|
|
NAME_FILTER = '{"name": "e2e.*"}'
|
|
NAME_FILTER_FOR_DEDUCED = '{"name": "e2e.test_webhook.deduced"}'
|
|
TYPE_FILTER = '{"vitrage_type": "doctor"}'
|
|
FILTER_NO_MATCH = '{"name": "NO MATCH"}'
|
|
NOTIFICATION = 'notification'
|
|
PAYLOAD = 'payload'
|
|
MAIN_FILTER = {NOTIFICATION,
|
|
PAYLOAD}
|
|
DOCTOR_ALARM_FILTER = {VProps.VITRAGE_ID,
|
|
VProps.RESOURCE,
|
|
VProps.NAME,
|
|
VProps.UPDATE_TIMESTAMP,
|
|
VProps.VITRAGE_TYPE,
|
|
VProps.VITRAGE_CATEGORY,
|
|
VProps.STATE,
|
|
VProps.VITRAGE_OPERATIONAL_SEVERITY}
|
|
RESOURCE_FILTER = {VProps.VITRAGE_ID,
|
|
VProps.ID,
|
|
VProps.NAME,
|
|
VProps.VITRAGE_CATEGORY,
|
|
VProps.UPDATE_TIMESTAMP,
|
|
VProps.VITRAGE_OPERATIONAL_STATE,
|
|
VProps.VITRAGE_TYPE}
|
|
messages = []
|
|
|
|
|
|
class TestWebhook(TestActionsBase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(TestWebhook, cls).setUpClass()
|
|
cls._template = v_utils.add_template("e2e_webhooks.yaml")
|
|
# Configure mock server.
|
|
cls.mock_server_port = _get_free_port()
|
|
cls.mock_server = MockHTTPServer(('localhost', cls.mock_server_port),
|
|
MockServerRequestHandler)
|
|
|
|
# Start running mock server in a separate thread.
|
|
cls.mock_server_thread = Thread(target=cls.mock_server.serve_forever)
|
|
cls.mock_server_thread.setDaemon(True)
|
|
cls.mock_server_thread.start()
|
|
cls.URL_PROPS = 'http://localhost:%s/' % cls.mock_server_port
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
if cls._template is not None:
|
|
v_utils.delete_template(cls._template['uuid'])
|
|
|
|
def tearDown(self):
|
|
super(TestWebhook, self).tearDown()
|
|
del messages[:]
|
|
self._delete_webhooks()
|
|
self.mock_server.reset_requests_list()
|
|
|
|
@utils.tempest_logger
|
|
def test_basic_event(self):
|
|
|
|
try:
|
|
|
|
# Add webhook with filter matching alarm
|
|
self.vitrage_client.webhook.add(url=self.URL_PROPS,
|
|
regex_filter=NAME_FILTER,
|
|
headers=HEADERS_PROPS)
|
|
|
|
# Raise alarm
|
|
self._trigger_do_action(TRIGGER_ALARM_1)
|
|
|
|
# Check event received
|
|
self.assertThat(self.mock_server.requests, matchers.HasLength(1),
|
|
'Wrong number of notifications for raise alarm')
|
|
|
|
# Undo
|
|
self._trigger_undo_action(TRIGGER_ALARM_1)
|
|
|
|
# Check event undo received
|
|
self.assertThat(self.mock_server.requests, matchers.HasLength(2),
|
|
'Wrong number of notifications for clear alarm')
|
|
|
|
finally:
|
|
self._trigger_undo_action(TRIGGER_ALARM_1)
|
|
|
|
@utils.tempest_logger
|
|
def test_with_no_filter(self):
|
|
"""Test to see that a webhook with no filter receives all
|
|
|
|
notifications
|
|
"""
|
|
|
|
try:
|
|
|
|
# Add webhook
|
|
self.vitrage_client.webhook.add(url=self.URL_PROPS,
|
|
regex_filter=NAME_FILTER)
|
|
|
|
# Raise alarm
|
|
self._trigger_do_action(TRIGGER_ALARM_1)
|
|
|
|
# Check event received
|
|
self.assertThat(self.mock_server.requests, matchers.HasLength(1),
|
|
'Wrong number of notifications for raise alarm')
|
|
|
|
# Raise another alarm
|
|
self._trigger_do_action(TRIGGER_ALARM_2)
|
|
|
|
# Check second event received
|
|
self.assertThat(self.mock_server.requests, matchers.HasLength(2),
|
|
'Wrong number of notifications for clear alarm')
|
|
|
|
finally:
|
|
self._trigger_undo_action(TRIGGER_ALARM_1)
|
|
self._trigger_undo_action(TRIGGER_ALARM_2)
|
|
|
|
@utils.tempest_logger
|
|
def test_with_no_match(self):
|
|
"""Test to check that filters with no match do not send event """
|
|
|
|
try:
|
|
|
|
# Add webhook
|
|
self.vitrage_client.webhook.add(url=self.URL_PROPS,
|
|
regex_filter=FILTER_NO_MATCH)
|
|
|
|
# Raise alarm
|
|
self._trigger_do_action(TRIGGER_ALARM_1)
|
|
|
|
# Check event not received
|
|
self.assertThat(self.mock_server.requests, IsEmpty(),
|
|
'event should not have passed filter')
|
|
|
|
# Raise another alarm
|
|
self._trigger_do_action(TRIGGER_ALARM_2)
|
|
|
|
# Check second event not received
|
|
self.assertThat(self.mock_server.requests, IsEmpty(),
|
|
'event should not have passed filter')
|
|
|
|
finally:
|
|
self._trigger_undo_action(TRIGGER_ALARM_1)
|
|
self._trigger_undo_action(TRIGGER_ALARM_2)
|
|
|
|
@utils.tempest_logger
|
|
def test_multiple_webhooks(self):
|
|
"""Test to check filter by type and with no filter (with 2 separate
|
|
|
|
webhooks)
|
|
"""
|
|
|
|
try:
|
|
|
|
# Add webhook
|
|
self.vitrage_client.webhook.add(url=self.URL_PROPS,
|
|
regex_filter=TYPE_FILTER)
|
|
|
|
self.vitrage_client.webhook.add(url=self.URL_PROPS)
|
|
|
|
# Raise alarm
|
|
self._trigger_do_action(TRIGGER_ALARM_1)
|
|
|
|
# Check event received
|
|
self.assertThat(self.mock_server.requests, matchers.HasLength(2),
|
|
'event not posted to all webhooks')
|
|
|
|
# Raise another alarm
|
|
self._trigger_do_action(TRIGGER_ALARM_2)
|
|
|
|
# Check second event received
|
|
self.assertThat(self.mock_server.requests, matchers.HasLength(4),
|
|
'event not posted to all webhooks')
|
|
|
|
finally:
|
|
self._trigger_undo_action(TRIGGER_ALARM_1)
|
|
self._trigger_undo_action(TRIGGER_ALARM_2)
|
|
|
|
@utils.tempest_logger
|
|
def test_for_deduced_alarm(self):
|
|
|
|
try:
|
|
# Add webhook with filter for the deduced alarm
|
|
self.vitrage_client.webhook.add(
|
|
url=self.URL_PROPS,
|
|
regex_filter=NAME_FILTER_FOR_DEDUCED,
|
|
headers=HEADERS_PROPS)
|
|
|
|
# Raise the trigger alarm
|
|
self._trigger_do_action(TRIGGER_ALARM_WITH_DEDUCED)
|
|
|
|
# Check event received - expected one for the deduced alarm
|
|
# (the trigger alarm does not pass the filter). This test verifies
|
|
# that the webhook is called only once for the deduced alarm.
|
|
time.sleep(1)
|
|
self.assertThat(self.mock_server.requests, matchers.HasLength(1),
|
|
'Wrong number of notifications for deduced alarm')
|
|
|
|
# Undo
|
|
self._trigger_undo_action(TRIGGER_ALARM_WITH_DEDUCED)
|
|
|
|
# Check event undo received
|
|
time.sleep(1)
|
|
self.assertThat(self.mock_server.requests, matchers.HasLength(2),
|
|
'Wrong number of notifications '
|
|
'for clear deduced alarm')
|
|
|
|
finally:
|
|
self._trigger_undo_action(TRIGGER_ALARM_WITH_DEDUCED)
|
|
|
|
@utils.tempest_logger
|
|
def test_payload_format(self):
|
|
|
|
try:
|
|
|
|
self.vitrage_client.webhook.add(url=self.URL_PROPS,
|
|
headers=HEADERS_PROPS)
|
|
|
|
# Raise the trigger alarm
|
|
self._trigger_do_action(TRIGGER_ALARM_1)
|
|
|
|
# pre check that correct amount of notifications sent
|
|
self.assertThat(self.mock_server.requests, matchers.HasLength(1),
|
|
'Wrong number of notifications for alarm')
|
|
self.assertThat(messages, matchers.HasLength(1),
|
|
'Wrong number of messages for alarm')
|
|
|
|
alarm = ast.literal_eval(messages[0])
|
|
|
|
# check that only specified fields are sent for the alarm,
|
|
# payload and resource
|
|
passed_filter = utils.filter_data(alarm,
|
|
MAIN_FILTER,
|
|
match_filter=False)
|
|
|
|
self.assertThat(passed_filter,
|
|
IsEmpty(),
|
|
"Wrong main fields sent")
|
|
|
|
payload = alarm.get(PAYLOAD)
|
|
if payload:
|
|
passed_filter = utils.filter_data(payload,
|
|
DOCTOR_ALARM_FILTER,
|
|
match_filter=False)
|
|
|
|
self.assertThat(passed_filter,
|
|
IsEmpty(),
|
|
"Wrong alarm fields sent")
|
|
|
|
sent_fields = utils.filter_data(payload, DOCTOR_ALARM_FILTER)
|
|
|
|
self.assert_set_equal(DOCTOR_ALARM_FILTER, sent_fields,
|
|
"Some alarm fields not sent")
|
|
|
|
resource = payload.get(VProps.RESOURCE)
|
|
if resource:
|
|
passed_filter = utils.filter_data(resource,
|
|
RESOURCE_FILTER,
|
|
match_filter=False)
|
|
|
|
self.assertThat(passed_filter,
|
|
IsEmpty(),
|
|
"Wrong resource fields sent")
|
|
|
|
sent_fields = utils.filter_data(resource, RESOURCE_FILTER)
|
|
|
|
self.assert_set_equal(RESOURCE_FILTER, sent_fields,
|
|
"Some resource fields not sent")
|
|
finally:
|
|
self._trigger_undo_action(TRIGGER_ALARM_1)
|
|
|
|
@classmethod
|
|
def _delete_webhooks(cls):
|
|
webhooks = cls.vitrage_client.webhook.list()
|
|
for webhook in webhooks:
|
|
cls.vitrage_client.webhook.delete(webhook['id'])
|
|
|
|
|
|
def _get_free_port():
|
|
|
|
s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
|
|
s.bind(('localhost', 0))
|
|
address, port = s.getsockname()
|
|
s.close()
|
|
return port
|
|
|
|
|
|
class MockHTTPServer(BaseHTTPServer.HTTPServer):
|
|
|
|
def __init__(self, server, handler):
|
|
BaseHTTPServer.HTTPServer.__init__(self, server, handler)
|
|
self.requests = []
|
|
|
|
def process_request(self, request, client_address):
|
|
self.requests.append(request)
|
|
LOG.info('received request: %s', request)
|
|
BaseHTTPServer.HTTPServer.process_request(
|
|
self, client_address=client_address, request=request)
|
|
|
|
def reset_requests_list(self):
|
|
self.requests = []
|
|
|
|
|
|
class MockServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
|
|
|
def do_POST(self):
|
|
# Process a HTTP Post request and return status code 200
|
|
|
|
content_len = int(self.headers.get('content-length', 0))
|
|
# save received JSON
|
|
messages.append(self.rfile.read(content_len).decode('utf-8'))
|
|
# send fake response
|
|
self.send_response(requests.codes.ok)
|
|
self.end_headers()
|
|
return
|
|
|
|
def log_message(self, format, *args):
|
|
pass
|