blob: 352c7e3ac6b98daccc705a5aac8e146163646b3b [file] [log] [blame]
# Copyright 2017-present Open Networking Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import, print_function
import confluent_kafka
import functools
import unittest
from mock import patch, PropertyMock, ANY
import os
import sys
import time
log = None
test_path = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
sync_lib_dir = os.path.join(test_path, "..", "xossynchronizer")
xos_dir = os.path.join(test_path, "..", "..", "..", "xos")
print(os.getcwd())
def config_get_mock(orig, overrides, key):
if key in overrides:
return overrides[key]
else:
return orig(key)
class FakeKafkaConsumer:
def __init__(self, values=[]):
self.values = values
def subscribe(self, topics):
pass
def poll(self, timeout=1.0):
if self.values:
return FakeKafkaMessage(self.values.pop())
# block forever
time.sleep(1000)
class FakeKafkaMessage:
""" Works like Message in confluent_kafka
https://docs.confluent.io/current/clients/confluent-kafka-python/#message
"""
def __init__(
self,
timestamp=None,
topic="faketopic",
key="fakekey",
value="fakevalue",
error=False,
):
if timestamp is None:
self.fake_ts_type = confluent_kafka.TIMESTAMP_NOT_AVAILABLE
self.fake_timestamp = None
else:
self.fake_ts_type = confluent_kafka.TIMESTAMP_CREATE_TIME
self.fake_timestamp = timestamp
self.fake_topic = topic
self.fake_key = key
self.fake_value = value
self.fake_error = error
def error(self):
return self.fake_error
def timestamp(self):
return (self.fake_ts_type, self.fake_timestamp)
def topic(self):
return self.fake_topic
def key(self):
return self.fake_key
def value(self):
return self.fake_value
class TestEventEngine(unittest.TestCase):
@classmethod
def setUpClass(cls):
global log
config = os.path.join(test_path, "test_config.yaml")
from xosconfig import Config
Config.clear()
Config.init(config, "synchronizer-config-schema.yaml")
if not log:
from multistructlog import create_logger
log = create_logger(Config().get("logging"))
def setUp(self):
global XOSKafkaThread, Config, log
self.sys_path_save = sys.path
self.cwd_save = os.getcwd()
config = os.path.join(test_path, "test_config.yaml")
from xosconfig import Config
Config.clear()
Config.init(config, "synchronizer-config-schema.yaml")
from xossynchronizer.mock_modelaccessor_build import (
build_mock_modelaccessor,
)
build_mock_modelaccessor(sync_lib_dir, xos_dir, services_dir=None, service_xprotos=[])
from xossynchronizer.modelaccessor import model_accessor
# The test config.yaml references files in `xos-synchronizer-tests/` so make sure we're in the parent
# directory of the test directory.
os.chdir(os.path.join(test_path, ".."))
from xossynchronizer.event_engine import XOSKafkaThread, XOSEventEngine
self.event_steps_dir = Config.get("event_steps_dir")
self.event_engine = XOSEventEngine(model_accessor=model_accessor, log=log)
def tearDown(self):
sys.path = self.sys_path_save
os.chdir(self.cwd_save)
def test_load_event_step_modules(self):
self.event_engine.load_event_step_modules(self.event_steps_dir)
self.assertEqual(len(self.event_engine.event_steps), 1)
def test_start(self):
self.event_engine.load_event_step_modules(self.event_steps_dir)
with patch.object(
XOSKafkaThread, "create_kafka_consumer"
) as create_kafka_consumer, patch.object(
FakeKafkaConsumer, "subscribe"
) as fake_subscribe, patch.object(
self.event_engine.event_steps[0], "process_event"
) as process_event:
create_kafka_consumer.return_value = FakeKafkaConsumer(
values=["sampleevent"]
)
self.event_engine.start()
self.assertEqual(len(self.event_engine.threads), 1)
# Since event_engine.start() launches threads, give them a hundred milliseconds to do something...
time.sleep(0.1)
# We should have subscribed to the fake consumer
fake_subscribe.assert_called_once()
# The fake consumer will have returned one event
process_event.assert_called_once()
def test_start_with_pattern(self):
self.event_engine.load_event_step_modules(self.event_steps_dir)
with patch.object(
XOSKafkaThread, "create_kafka_consumer"
) as create_kafka_consumer, patch.object(
FakeKafkaConsumer, "subscribe"
) as fake_subscribe, patch.object(
self.event_engine.event_steps[0], "process_event"
) as process_event, patch.object(
self.event_engine.event_steps[0], "pattern", new_callable=PropertyMock
) as pattern, patch.object(
self.event_engine.event_steps[0], "topics", new_callable=PropertyMock
) as topics:
pattern.return_value = "somepattern"
topics.return_value = []
create_kafka_consumer.return_value = FakeKafkaConsumer(
values=["sampleevent"]
)
self.event_engine.start()
self.assertEqual(len(self.event_engine.threads), 1)
# Since event_engine.start() launches threads, give them a hundred milliseconds to do something...
time.sleep(0.1)
# We should have subscribed to the fake consumer
fake_subscribe.assert_called_with("somepattern")
# The fake consumer will have returned one event
process_event.assert_called_once()
def test_start_bad_tech(self):
""" Set an unknown Technology in the event_step. XOSEventEngine.start() should print an error message and
not create any threads.
"""
self.event_engine.load_event_step_modules(self.event_steps_dir)
with patch.object(
XOSKafkaThread, "create_kafka_consumer"
) as create_kafka_consumer, patch.object(
log, "error"
) as log_error, patch.object(
self.event_engine.event_steps[0], "technology"
) as technology:
technology.return_value = "not_kafka"
create_kafka_consumer.return_value = FakeKafkaConsumer()
self.event_engine.start()
self.assertEqual(len(self.event_engine.threads), 0)
log_error.assert_called_with(
"Unknown technology. Skipping step",
step="TestEventStep",
technology=ANY,
)
def test_start_bad_no_topics(self):
""" Set no topics in the event_step. XOSEventEngine.start() will launch a thread, but the thread will fail
with an exception before calling subscribe.
"""
self.event_engine.load_event_step_modules(self.event_steps_dir)
with patch.object(
XOSKafkaThread, "create_kafka_consumer"
) as create_kafka_consumer, patch.object(
FakeKafkaConsumer, "subscribe"
) as fake_subscribe, patch.object(
self.event_engine.event_steps[0], "topics", new_callable=PropertyMock
) as topics:
topics.return_value = []
create_kafka_consumer.return_value = FakeKafkaConsumer()
self.event_engine.start()
# the thread does get launched, but it will fail with an exception
self.assertEqual(len(self.event_engine.threads), 1)
time.sleep(0.1)
fake_subscribe.assert_not_called()
def test_start_bad_topics_and_pattern(self):
""" Set no topics in the event_step. XOSEventEngine.start() will launch a thread, but the thread will fail
with an exception before calling subscribe.
"""
self.event_engine.load_event_step_modules(self.event_steps_dir)
with patch.object(
XOSKafkaThread, "create_kafka_consumer"
) as create_kafka_consumer, patch.object(
FakeKafkaConsumer, "subscribe"
) as fake_subscribe, patch.object(
self.event_engine.event_steps[0], "pattern", new_callable=PropertyMock
) as pattern:
pattern.return_value = "foo"
create_kafka_consumer.return_value = FakeKafkaConsumer()
self.event_engine.start()
# the thread does get launched, but it will fail with an exception
self.assertEqual(len(self.event_engine.threads), 1)
time.sleep(0.1)
fake_subscribe.assert_not_called()
def test_start_config_no_eventbus_kind(self):
""" Set a blank event_bus.kind in Config. XOSEventEngine.start() should print an error message and
not create any threads.
"""
self.event_engine.load_event_step_modules(self.event_steps_dir)
config_get_orig = Config.get
with patch.object(
XOSKafkaThread, "create_kafka_consumer"
) as create_kafka_consumer, patch.object(
log, "error"
) as log_error, patch.object(
Config,
"get",
new=functools.partial(
config_get_mock, config_get_orig, {"event_bus.kind": None}
),
):
create_kafka_consumer.return_value = FakeKafkaConsumer()
self.event_engine.start()
self.assertEqual(len(self.event_engine.threads), 0)
log_error.assert_called_with(
"Eventbus kind is not configured in synchronizer config file."
)
def test_start_config_bad_eventbus_kind(self):
""" Set an unknown event_bus.kind in Config. XOSEventEngine.start() should print an error message and
not create any threads.
"""
self.event_engine.load_event_step_modules(self.event_steps_dir)
config_get_orig = Config.get
with patch.object(
XOSKafkaThread, "create_kafka_consumer"
) as create_kafka_consumer, patch.object(
log, "error"
) as log_error, patch.object(
Config,
"get",
new=functools.partial(
config_get_mock, config_get_orig, {"event_bus.kind": "not_kafka"}
),
):
create_kafka_consumer.return_value = FakeKafkaConsumer()
self.event_engine.start()
self.assertEqual(len(self.event_engine.threads), 0)
log_error.assert_called_with(
"Eventbus kind is set to a technology we do not implement.",
eventbus_kind="not_kafka",
)
if __name__ == "__main__":
unittest.main()