Merge "fix deduced alarm test and add test to check correct payload filter"
This commit is contained in:
commit
ba6366aff9
182
vitrage_tempest_tests/tests/common/constants.py
Normal file
182
vitrage_tempest_tests/tests/common/constants.py
Normal file
@ -0,0 +1,182 @@
|
||||
# Copyright 2015 - Alcatel-Lucent
|
||||
# Copyright 2016 - 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.
|
||||
|
||||
|
||||
class VertexProperties(object):
|
||||
VITRAGE_CATEGORY = 'vitrage_category'
|
||||
VITRAGE_TYPE = 'vitrage_type'
|
||||
VITRAGE_ID = 'vitrage_id'
|
||||
VITRAGE_STATE = 'vitrage_state'
|
||||
VITRAGE_IS_DELETED = 'vitrage_is_deleted'
|
||||
VITRAGE_IS_PLACEHOLDER = 'vitrage_is_placeholder'
|
||||
VITRAGE_SAMPLE_TIMESTAMP = 'vitrage_sample_timestamp'
|
||||
VITRAGE_AGGREGATED_STATE = 'vitrage_aggregated_state'
|
||||
VITRAGE_OPERATIONAL_STATE = 'vitrage_operational_state'
|
||||
VITRAGE_AGGREGATED_SEVERITY = 'vitrage_aggregated_severity'
|
||||
VITRAGE_OPERATIONAL_SEVERITY = 'vitrage_operational_severity'
|
||||
VITRAGE_RESOURCE_ID = 'vitrage_resource_id'
|
||||
ID = 'id'
|
||||
STATE = 'state'
|
||||
PROJECT_ID = 'project_id'
|
||||
UPDATE_TIMESTAMP = 'update_timestamp'
|
||||
NAME = 'name'
|
||||
SEVERITY = 'severity'
|
||||
IS_MARKED_DOWN = 'is_marked_down'
|
||||
INFO = 'info'
|
||||
GRAPH_INDEX = 'graph_index'
|
||||
RAWTEXT = 'rawtext'
|
||||
RESOURCE_ID = 'resource_id'
|
||||
RESOURCE_NAME = 'resource_name'
|
||||
VITRAGE_RESOURCE_TYPE = 'vitrage_resource_type'
|
||||
RESOURCE = 'resource'
|
||||
IS_REAL_VITRAGE_ID = 'is_real_vitrage_id'
|
||||
|
||||
|
||||
class EdgeProperties(object):
|
||||
RELATIONSHIP_TYPE = 'relationship_type'
|
||||
VITRAGE_IS_DELETED = 'vitrage_is_deleted'
|
||||
UPDATE_TIMESTAMP = 'update_timestamp'
|
||||
|
||||
|
||||
class EdgeLabel(object):
|
||||
"""Define *some* edge labels
|
||||
|
||||
Note that edge labels are not restricted to the values in this class, and
|
||||
other datasources can defined their own edge labels.
|
||||
"""
|
||||
ON = 'on'
|
||||
CONTAINS = 'contains'
|
||||
CAUSES = 'causes'
|
||||
ATTACHED = 'attached'
|
||||
ATTACHED_PUBLIC = 'attached_public'
|
||||
ATTACHED_PRIVATE = 'attached_private'
|
||||
CONNECT = 'connect'
|
||||
MANAGED_BY = 'managed_by'
|
||||
COMPRISED = 'comprised'
|
||||
|
||||
@staticmethod
|
||||
def labels():
|
||||
return [value for label, value in vars(EdgeLabel).items()
|
||||
if not label.startswith(('_', 'labels'))]
|
||||
|
||||
|
||||
class DatasourceAction(object):
|
||||
SNAPSHOT = 'snapshot'
|
||||
INIT_SNAPSHOT = 'init_snapshot'
|
||||
UPDATE = 'update'
|
||||
|
||||
|
||||
class UpdateMethod(object):
|
||||
NONE = 'none'
|
||||
PULL = 'pull'
|
||||
PUSH = 'push'
|
||||
|
||||
|
||||
class EntityCategory(object):
|
||||
RESOURCE = 'RESOURCE'
|
||||
ALARM = 'ALARM'
|
||||
|
||||
@staticmethod
|
||||
def categories():
|
||||
return [value for category, value in vars(EntityCategory).items()
|
||||
if not category.startswith(('_', 'categories'))]
|
||||
|
||||
|
||||
class DatasourceProperties(object):
|
||||
ENTITY_TYPE = 'vitrage_entity_type'
|
||||
DATASOURCE_ACTION = 'vitrage_datasource_action'
|
||||
SAMPLE_DATE = 'vitrage_sample_date'
|
||||
EVENT_TYPE = 'vitrage_event_type'
|
||||
|
||||
|
||||
class GraphAction(object):
|
||||
CREATE_ENTITY = 'create_entity'
|
||||
DELETE_ENTITY = 'delete_entity'
|
||||
UPDATE_ENTITY = 'update_entity'
|
||||
DELETE_RELATIONSHIP = 'delete_relationship'
|
||||
UPDATE_RELATIONSHIP = 'update_relationship'
|
||||
REMOVE_DELETED_ENTITY = 'remove_deleted_entity'
|
||||
END_MESSAGE = 'end_message'
|
||||
|
||||
|
||||
class NotifierEventTypes(object):
|
||||
ACTIVATE_DEDUCED_ALARM_EVENT = 'vitrage.deduced_alarm.activate'
|
||||
DEACTIVATE_DEDUCED_ALARM_EVENT = 'vitrage.deduced_alarm.deactivate'
|
||||
ACTIVATE_ALARM_EVENT = 'vitrage.alarm.activate'
|
||||
DEACTIVATE_ALARM_EVENT = 'vitrage.alarm.deactivate'
|
||||
ACTIVATE_MARK_DOWN_EVENT = 'vitrage.mark_down.activate'
|
||||
DEACTIVATE_MARK_DOWN_EVENT = 'vitrage.mark_down.deactivate'
|
||||
EXECUTE_EXTERNAL_ACTION = 'vitrage.execute_external_action'
|
||||
|
||||
|
||||
class TemplateTopologyFields(object):
|
||||
"""yaml fields for topology definitions"""
|
||||
METADATA = 'metadata'
|
||||
DESCRIPTION = 'description'
|
||||
NAME = 'name'
|
||||
VERSION = 'version'
|
||||
|
||||
DEFINITIONS = 'definitions'
|
||||
|
||||
ENTITIES = 'entities'
|
||||
ENTITY = 'entity'
|
||||
TYPE = 'type'
|
||||
ID = 'id'
|
||||
|
||||
RELATIONSHIPS = 'relationships'
|
||||
RELATIONSHIP = 'relationship'
|
||||
RELATIONSHIP_TYPE = 'relationship_type'
|
||||
SOURCE = 'source'
|
||||
TARGET = 'target'
|
||||
|
||||
|
||||
class EventProperties(object):
|
||||
TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%f'
|
||||
TYPE = 'type'
|
||||
TIME = 'time'
|
||||
DETAILS = 'details'
|
||||
|
||||
|
||||
class DatasourceOpts(object):
|
||||
TRANSFORMER = 'transformer'
|
||||
DRIVER = 'driver'
|
||||
UPDATE_METHOD = 'update_method'
|
||||
CHANGES_INTERVAL = 'changes_interval'
|
||||
CONFIG_FILE = 'config_file'
|
||||
|
||||
|
||||
class TemplateTypes(object):
|
||||
STANDARD = 'standard'
|
||||
DEFINITION = 'definition'
|
||||
EQUIVALENCE = 'equivalence'
|
||||
|
||||
@staticmethod
|
||||
def types():
|
||||
return [value for type, value in vars(TemplateTypes).items()
|
||||
if not type.startswith(('_', 'types'))]
|
||||
|
||||
|
||||
class TemplateStatus(object):
|
||||
ACTIVE = 'ACTIVE'
|
||||
ERROR = 'ERROR'
|
||||
DELETING = 'DELETING'
|
||||
DELETED = 'DELETED'
|
||||
LOADING = 'LOADING'
|
||||
|
||||
|
||||
class TenantProps(object):
|
||||
ALL_TENANTS = 'all_tenants'
|
||||
TENANT = 'tenant'
|
||||
IS_ADMIN = 'is_admin'
|
@ -11,17 +11,24 @@
|
||||
# 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
|
||||
|
||||
from oslo_log import log as logging
|
||||
import requests
|
||||
from six.moves import BaseHTTPServer
|
||||
import socket
|
||||
from threading import Thread
|
||||
import time
|
||||
|
||||
from vitrage_tempest_tests.tests.common.constants import VertexProperties as \
|
||||
VProps
|
||||
from vitrage_tempest_tests.tests.common.tempest_clients import TempestClients
|
||||
from vitrage_tempest_tests.tests.common import vitrage_utils as v_utils
|
||||
|
||||
from vitrage_tempest_tests.tests.e2e.test_actions_base import TestActionsBase
|
||||
from vitrage_tempest_tests.tests import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
TRIGGER_ALARM_1 = 'e2e.test_webhook.alarm1'
|
||||
@ -35,6 +42,26 @@ 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.UPDATE_TIMESTAMP,
|
||||
VProps.VITRAGE_OPERATIONAL_STATE,
|
||||
VProps.VITRAGE_TYPE,
|
||||
)
|
||||
messages = []
|
||||
|
||||
|
||||
class TestWebhook(TestActionsBase):
|
||||
@ -42,6 +69,7 @@ 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),
|
||||
@ -53,6 +81,20 @@ class TestWebhook(TestActionsBase):
|
||||
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 setUp(self):
|
||||
super(TestWebhook, self).setUp()
|
||||
|
||||
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):
|
||||
|
||||
@ -80,9 +122,7 @@ class TestWebhook(TestActionsBase):
|
||||
'Wrong number of notifications for clear alarm')
|
||||
|
||||
finally:
|
||||
self._delete_webhooks()
|
||||
self._trigger_undo_action(TRIGGER_ALARM_1)
|
||||
self.mock_server.reset_requests_list()
|
||||
|
||||
@utils.tempest_logger
|
||||
def test_with_no_filter(self):
|
||||
@ -114,10 +154,8 @@ class TestWebhook(TestActionsBase):
|
||||
'Wrong number of notifications for clear alarm')
|
||||
|
||||
finally:
|
||||
self._delete_webhooks()
|
||||
self._trigger_undo_action(TRIGGER_ALARM_1)
|
||||
self._trigger_undo_action(TRIGGER_ALARM_2)
|
||||
self.mock_server.reset_requests_list()
|
||||
|
||||
@utils.tempest_logger
|
||||
def test_with_no_match(self):
|
||||
@ -146,14 +184,12 @@ class TestWebhook(TestActionsBase):
|
||||
'event should not have passed filter')
|
||||
|
||||
finally:
|
||||
self._delete_webhooks()
|
||||
self._trigger_undo_action(TRIGGER_ALARM_1)
|
||||
self._trigger_undo_action(TRIGGER_ALARM_2)
|
||||
self.mock_server.reset_requests_list()
|
||||
|
||||
@utils.tempest_logger
|
||||
def test_multiple_webhooks(self):
|
||||
"""Test to check filter by type and by ID (with 2 different
|
||||
"""Test to check filter by type and with no filter (with 2 separate
|
||||
|
||||
webhooks)
|
||||
"""
|
||||
@ -185,46 +221,103 @@ class TestWebhook(TestActionsBase):
|
||||
'event not posted to all webhooks')
|
||||
|
||||
finally:
|
||||
self._delete_webhooks()
|
||||
self._trigger_undo_action(TRIGGER_ALARM_1)
|
||||
self._trigger_undo_action(TRIGGER_ALARM_2)
|
||||
self.mock_server.reset_requests_list()
|
||||
|
||||
# Will be un-commented-out in the next change
|
||||
#
|
||||
# @utils.tempest_logger
|
||||
# def test_webhook_for_deduced_alarm(self):
|
||||
#
|
||||
# try:
|
||||
#
|
||||
# # Add webhook with filter for the deduced alarm
|
||||
# TempestClients.vitrage().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.
|
||||
# self.assertEqual(1, len(self.mock_server.requests),
|
||||
# 'Wrong number of notifications for deduced alarm')
|
||||
#
|
||||
# # Undo
|
||||
# self._trigger_undo_action(TRIGGER_ALARM_WITH_DEDUCED)
|
||||
#
|
||||
# # Check event undo received
|
||||
# self.assertEqual(2, len(self.mock_server.requests),
|
||||
# 'Wrong number of notifications for clear deduced '
|
||||
# 'alarm')
|
||||
#
|
||||
# finally:
|
||||
# self._delete_webhooks()
|
||||
# self._trigger_undo_action(TRIGGER_ALARM_WITH_DEDUCED)
|
||||
# self.mock_server.reset_requests_list()
|
||||
@utils.tempest_logger
|
||||
def test_for_deduced_alarm(self):
|
||||
|
||||
try:
|
||||
# Add webhook with filter for the deduced alarm
|
||||
TempestClients.vitrage().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.assertEqual(1, len(self.mock_server.requests),
|
||||
'Wrong number of notifications for deduced alarm')
|
||||
|
||||
# Undo
|
||||
self._trigger_undo_action(TRIGGER_ALARM_WITH_DEDUCED)
|
||||
|
||||
# Check event undo received
|
||||
time.sleep(1)
|
||||
self.assertEqual(2, len(self.mock_server.requests),
|
||||
'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:
|
||||
|
||||
TempestClients.vitrage().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.assertEqual(1, len(self.mock_server.requests),
|
||||
'Wrong number of notifications for alarm')
|
||||
self.assertEqual(1, len(messages),
|
||||
'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, False)
|
||||
|
||||
self.assertEqual(0, len(passed_filter),
|
||||
"Wrong main fields sent")
|
||||
|
||||
payload = alarm.get(PAYLOAD)
|
||||
if payload:
|
||||
passed_filter = utils.filter_data(payload,
|
||||
DOCTOR_ALARM_FILTER,
|
||||
False)
|
||||
|
||||
self.assertEqual(0, len(passed_filter),
|
||||
"Wrong alarm fields sent")
|
||||
|
||||
sent_fields = utils.filter_data(payload,
|
||||
DOCTOR_ALARM_FILTER,
|
||||
True)
|
||||
|
||||
self.assertEqual(len(sent_fields), len(DOCTOR_ALARM_FILTER),
|
||||
"Some alarm fields not sent")
|
||||
|
||||
resource = payload.get(VProps.RESOURCE)
|
||||
if resource:
|
||||
passed_filter = utils.filter_data(resource,
|
||||
RESOURCE_FILTER,
|
||||
False)
|
||||
|
||||
self.assertEqual(0, len(passed_filter),
|
||||
"Wrong resource fields sent")
|
||||
|
||||
sent_fields = utils.filter_data(resource,
|
||||
RESOURCE_FILTER,
|
||||
True)
|
||||
|
||||
self.assertEqual(len(sent_fields), len(RESOURCE_FILTER),
|
||||
"Some resource fields not sent")
|
||||
finally:
|
||||
self._trigger_undo_action(TRIGGER_ALARM_1)
|
||||
|
||||
def _delete_webhooks(self):
|
||||
webhooks = TempestClients.vitrage().webhook.list()
|
||||
@ -233,6 +326,7 @@ class TestWebhook(TestActionsBase):
|
||||
|
||||
|
||||
def _get_free_port():
|
||||
|
||||
s = socket.socket(socket.AF_INET, type=socket.SOCK_STREAM)
|
||||
s.bind(('localhost', 0))
|
||||
address, port = s.getsockname()
|
||||
@ -259,8 +353,12 @@ class MockHTTPServer(BaseHTTPServer.HTTPServer):
|
||||
class MockServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
|
||||
def do_POST(self):
|
||||
|
||||
# Process a HTTP Post request and return status code 200
|
||||
|
||||
content_len = int(self.headers.getheader('content-length', 0))
|
||||
# save received JSON
|
||||
messages.append(str(self.rfile.read(content_len)))
|
||||
# send fake response
|
||||
self.send_response(requests.codes.ok)
|
||||
self.end_headers()
|
||||
return
|
||||
|
@ -169,3 +169,9 @@ def wait_for_status(max_waiting, func, **kwargs):
|
||||
time.sleep(2)
|
||||
LOG.error("wait_for_status - False")
|
||||
return False
|
||||
|
||||
|
||||
def filter_data(data, filter, match_filter=True):
|
||||
if match_filter:
|
||||
return [k for k in data if k in filter]
|
||||
return [k for k in data if k not in filter]
|
||||
|
Loading…
x
Reference in New Issue
Block a user