diff --git a/.drone.yml b/.drone.yml index 2924cfd..a3e6b8c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -4,7 +4,7 @@ name: default steps: - name: static_analysis - image: python:3 + image: python:3.8 commands: - pip3 install pylint bandit mccabe - pip3 install -r requirements.txt @@ -13,6 +13,12 @@ steps: - find . -name "*.py" -exec python3 -m mccabe --min 3 '{}' + || if [ $? -eq 1 ]; then echo "you fail"; fi - bandit -r . + || if [ $? -eq 1 ]; then echo "you fail"; fi +- name: unit_test + image: python:3.8 + commands: + - pip3 install -r requirements.txt + - pytest test.py + - name: build image: docker:stable-dind volumes: diff --git a/Dockerfile b/Dockerfile index 0ca2e4e..1875003 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3 +FROM python:3.8 WORKDIR /app diff --git a/app.py b/app.py index b808775..bbb6f7c 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,6 @@ #!/usr/bin/env python + +import os import random import uuid import logging @@ -21,12 +23,14 @@ sentry_sdk.init("https://3fa5ae886ba1489092ad49a93cb419c1@sentry.kmlabz.com/9") logging.basicConfig(level=logging.INFO) LOGGER = logging.getLogger(__name__) +KNOWNCONSUMER = os.getenv("PRODUCER_KNOWNCONSUMER",'10.69.42.1') + if __name__ == "__main__": LOGGER.info("Producer started") generateduuid = str(uuid) + communicator = Communicator(currentconsumer=KNOWNCONSUMER, uuid=generateduuid) LOGGER.debug(f"My uuid is {generateduuid}") - consumerlocator = ConsumerLocator(uuid=generateduuid) - communicator = Communicator(consumerlocator=consumerlocator,uuid=generateduuid) + consumerlocator = ConsumerLocator(uuid=generateduuid, communicator=communicator) messagesender = MessageSender(communicator=communicator) while True: diff --git a/communicator.py b/communicator.py index 69a7511..8328ad1 100644 --- a/communicator.py +++ b/communicator.py @@ -1,8 +1,6 @@ #!/usr/bin/env python import logging -import random import requests -from consumerlocator import ConsumerLocator """ Communicator module @@ -21,13 +19,13 @@ class Communicator: Class handling low level communication with consumers. """ - def __init__(self, consumerlocator: ConsumerLocator, uuid: str): + def __init__(self, currentconsumer: str, uuid: str): """ Initialize object :param consumerlocator: :param uuid: """ - self.consumerlocator=consumerlocator + self.currenctconsumer=currentconsumer self.uuid = uuid def sendmessage(self, message: str) -> None: @@ -36,7 +34,7 @@ class Communicator: :param message: :return: none """ - currentconsumer=self.consumerlocator.getcurrentconsumer() + currentconsumer=self.currenctconsumer LOGGER.debug(f"Sending message to {currentconsumer}") requests.post(f'http://{currentconsumer}/log', json={'uuid': self.uuid, 'message': message}) @@ -45,7 +43,7 @@ class Communicator: Get the list of available consumer from the current primary consumer. :return: """ - currentconsumer = self.consumerlocator.getcurrentconsumer() + currentconsumer = self.currenctconsumer response = requests.get(f'http://{currentconsumer}/consumer') json = response.json() LOGGER.debug(f"List of currently available consumers: {json}") @@ -56,9 +54,13 @@ class Communicator: Readiness probe primary consumer. :return: """ - currentconsumer = self.consumerlocator.getcurrentconsumer() - response = requests.get(f'http://{currentconsumer}/consumer') - isavailable = response.status_code == 200 + currentconsumer = self.currenctconsumer + try: + response = requests.get(f'http://{currentconsumer}/consumer') + isavailable = response.status_code == 200 + except Exception as e: + LOGGER.exception(e) + isavailable = False LOGGER.debug(f"Current consumer availability: {isavailable}") return isavailable @@ -68,7 +70,19 @@ class Communicator: :param consumer: :return: """ - response = requests.get(f'http://{consumer}/consumer') - isavailable = response.status_code == 200 + try: + response = requests.get(f'http://{consumer}/consumer') + isavailable = response.status_code == 200 + except Exception as e: + LOGGER.exception(e) + isavailable = False LOGGER.debug(f"Consumer {consumer} availability: {isavailable}") return isavailable + + def set_currentconsumer(self,currenctconsumer): + """ + Set current consumer + :param currenctconsumer: + :return: + """ + self.currenctconsumer=currenctconsumer diff --git a/consumerlocator.py b/consumerlocator.py index 0a9c55f..83988b4 100644 --- a/consumerlocator.py +++ b/consumerlocator.py @@ -13,6 +13,7 @@ __copyright__ = "Copyright 2020, GoldenPogácsa Team" __module_name__ = "consumerlocator" __version__text__ = "1" +KNOWNCONSUMER = os.getenv("PRODUCER_KNOWNCONSUMER",'10.69.42.1') class ConsumerLocator: @@ -20,13 +21,13 @@ class ConsumerLocator: Manages the list of consumers. """ - def __init__(self, uuid: str): + def __init__(self, uuid: str, communicator: Communicator): """ Initialize class. """ - self.consumerlist = [{"Host": os.environ["KnownConsumer"], "State": True, "LastOk": datetime.datetime.now()}] + self.consumerlist = [{"Host": KNOWNCONSUMER, "State": True, "LastOk": datetime.datetime.now()}] self.currentconsumer = self.consumerlist[0] - self.communicator = Communicator(consumerlocator=self,uuid=uuid) + self.communicator = communicator def learnconsumerlist(self) -> None: """" @@ -82,6 +83,7 @@ class ConsumerLocator: self.learnconsumerlist() if self.currentconsumer is not None: + self.communicator.set_currentconsumer(self.currentconsumer["Host"]) return self.currentconsumer["Host"] else: return None diff --git a/messagesender.py b/messagesender.py index 7c8d2cc..27992ae 100644 --- a/messagesender.py +++ b/messagesender.py @@ -28,10 +28,10 @@ class MessageSender: """ self.communicator = communicator - def randomstring(self, stringLength) -> str: + def randomstring(self, stringlength: int) -> str: """Generate a random string of fixed length """ letters = string.ascii_lowercase - return ''.join(random.choice(letters) for i in range(stringLength)) + return ''.join(random.choice(letters) for i in range(stringlength)) def sendmessage(self, message: str = "") -> None: """ diff --git a/requirements.txt b/requirements.txt index 07f9470..60339c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ sentry_sdk -requests \ No newline at end of file +requests +pytest +pytest-mock +pytest-httpserver \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..9e14434 --- /dev/null +++ b/test.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python + +import re +import consumerlocator +import communicator +import messagesender + +""" +Unit tests for producer module. +""" + +__author__ = "@tormakris" +__copyright__ = "Copyright 2020, GoldenPogácsa Team" +__module_name__ = "test" +__version__text__ = "1" + +generateduuid = 'c959ad81-58f9-4445-aab4-8f3d68aee1ad' + + +def test_generate_string(mocker): + mocker.patch('communicator.Communicator') + comm = communicator.Communicator( + currentconsumer="localhost", + uuid=generateduuid) + mess = messagesender.MessageSender(communicator=comm) + msg = mess.randomstring(stringlength=32) + assert isinstance(msg, str) + assert len(msg) == 32 + + +def test_sendmessage(httpserver): + httpserver.expect_oneshot_request( + uri="/log", + method='POST', + data="{\"uuid\": \"c959ad81-58f9-4445-aab4-8f3d68aee1ad\", \"message\": \"SENDING\"}").respond_with_json( + { + "test": "ok"}) + url = httpserver.url_for("/") + port = re.match(r"\W*http[^:]*\D*(\d+)", url).group(1) + comm = communicator.Communicator( + currentconsumer=f"127.0.0.1:{port}", + uuid=generateduuid) + mess = "SENDING" + ret = comm.sendmessage(message=mess) + assert ret is None + + +def test_send_message(mocker): + mocker.patch('communicator.Communicator') + comm = communicator.Communicator( + currentconsumer="127.0.0.1", + uuid=generateduuid) + mess = messagesender.MessageSender(communicator=comm) + messa = "SENDING" + msg = mess.sendmessage(message=messa) + assert msg is None + + +def test_discoveravailableconsumers(httpserver): + httpserver.expect_oneshot_request( + uri="/consumer", + method='GET', + data="").respond_with_json( + ["10.69.42.1", "10.10.10.10", "10.20.30.40"]) + url = httpserver.url_for("/") + port = re.match(r"\W*http[^:]*\D*(\d+)", url).group(1) + comm = communicator.Communicator( + currentconsumer=f"127.0.0.1:{port}", + uuid=generateduuid) + ret = comm.discoveravailableconsumers() + assert isinstance(ret, list) + assert ret == ["10.69.42.1", "10.10.10.10", "10.20.30.40"] + + +def test_isconsumeravailable(httpserver): + httpserver.expect_oneshot_request( + uri="/consumer", + method='GET', + data="").respond_with_json( + ["10.69.42.1", "10.10.10.10", "10.20.30.40"]) + url = httpserver.url_for("/") + port = re.match(r"\W*http[^:]*\D*(\d+)", url).group(1) + comm = communicator.Communicator( + currentconsumer=f"127.0.0.1:{port}", + uuid=generateduuid) + ret = comm.isconsumeravailable() + assert isinstance(ret, bool) + assert ret + + ret2 = comm.isconsumeravailable() + assert isinstance(ret2, bool) + assert ret2 == False + + comm2 = communicator.Communicator( + currentconsumer="127.0.0.1:69", + uuid=generateduuid) + + ret3 = comm2.isconsumeravailable() + assert isinstance(ret3, bool) + assert ret3 == False + + +def test_checkconsumer(httpserver): + httpserver.expect_oneshot_request( + uri="/consumer", + method='GET', + data="").respond_with_json( + ["10.69.42.1", "10.10.10.10", "10.20.30.40"]) + url = httpserver.url_for("/") + port = re.match(r"\W*http[^:]*\D*(\d+)", url).group(1) + comm = communicator.Communicator( + currentconsumer="127.0.0.1", + uuid=generateduuid) + ret = comm.checkconsumer(f"127.0.0.1:{port}") + assert isinstance(ret, bool) + assert ret + + ret2 = comm.checkconsumer(f"127.0.0.1:{port}") + assert isinstance(ret2, bool) + assert ret2 == False + + comm2 = communicator.Communicator( + currentconsumer="127.0.0.1", + uuid=generateduuid) + + ret3 = comm2.checkconsumer(f"127.0.0.1:{port}") + assert isinstance(ret3, bool) + assert ret3 == False + + +def test_setcurrentconsumer(): + comm = communicator.Communicator( + currentconsumer="127.0.0.1", + uuid=generateduuid) + comm.set_currentconsumer("10.69.42.1") + assert comm.currenctconsumer == "10.69.42.1" + + +def test_learnconsumerlist(httpserver): + httpserver.expect_request( + uri="/consumer", + method='GET', + data="").respond_with_json( + ["10.69.42.1", "10.10.10.10", "10.20.30.40"]) + url = httpserver.url_for("/") + port = re.match(r"\W*http[^:]*\D*(\d+)", url).group(1) + comm = communicator.Communicator( + currentconsumer=f"127.0.0.1:{port}", + uuid=generateduuid) + consumerlocator.KNOWNCONSUMER = f"127.0.0.1:{port}" + locator = consumerlocator.ConsumerLocator( + uuid=generateduuid, communicator=comm) + ret = locator.learnconsumerlist() + assert ret is None + + +def test_getcurrentconsumer(mocker): + mocker.patch('communicator.Communicator') + comm = communicator.Communicator( + currentconsumer="127.0.0.1", + uuid=generateduuid) + locator = consumerlocator.ConsumerLocator( + uuid=generateduuid, communicator=comm) + assert locator.getcurrentconsumer() == consumerlocator.KNOWNCONSUMER + + +def test_checkcurrentconsumer(httpserver): + httpserver.expect_oneshot_request( + uri="/consumer", + method='GET', + data="").respond_with_json( + ["10.69.42.1", "10.10.10.10", "10.20.30.40"]) + url = httpserver.url_for("/") + port = re.match(r"\W*http[^:]*\D*(\d+)", url).group(1) + comm = communicator.Communicator( + currentconsumer=f"127.0.0.1:{port}", + uuid=generateduuid) + consumerlocator.KNOWNCONSUMER = f"127.0.0.1:{port}" + locator = consumerlocator.ConsumerLocator( + uuid=generateduuid, communicator=comm) + ret = locator.checkcurrentconsumer() + assert ret == True + + +def test_updateconsumer(httpserver): + httpserver.expect_oneshot_request( + uri="/consumer", + method='GET', + data="").respond_with_json( + ["10.69.42.1", "10.10.10.10", "10.20.30.40"]) + url = httpserver.url_for("/") + port = re.match(r"\W*http[^:]*\D*(\d+)", url).group(1) + comm = communicator.Communicator( + currentconsumer=f"127.0.0.1:{port}", + uuid=generateduuid) + consumerlocator.KNOWNCONSUMER = f"127.0.0.1:{port}" + locator = consumerlocator.ConsumerLocator( + uuid=generateduuid, communicator=comm) + assert locator.currentconsumer is not None + ret = locator.updateconsumer() + assert ret == f"127.0.0.1:{port}" + + +def test_updateconsumerlist(httpserver): + httpserver.expect_oneshot_request( + uri="/consumer", + method='GET', + data="").respond_with_json( + ["10.69.42.1", "10.10.10.10", "10.20.30.40"]) + url = httpserver.url_for("/") + port = re.match(r"\W*http[^:]*\D*(\d+)", url).group(1) + comm = communicator.Communicator( + currentconsumer=f"127.0.0.1:{port}", + uuid=generateduuid) + consumerlocator.KNOWNCONSUMER = f"127.0.0.1:{port}" + locator = consumerlocator.ConsumerLocator( + uuid=generateduuid, communicator=comm) + ret = locator.updateconsumerlist() + assert ret is None