fix unit tests
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Torma Kristóf 2020-04-29 12:31:33 +02:00
commit c5a3fb2d04
Signed by: tormakris
GPG Key ID: DC83C4F2C41B1047
8 changed files with 73 additions and 42 deletions

View File

@ -15,26 +15,24 @@ steps:
- name: unit_test - name: unit_test
image: python:3.8 image: python:3.8
environment:
PRODUCER_REDIS: cache
commands: commands:
- pip3 install -r requirements.txt - pip3 install -r requirements.txt
- pytest test.py - pytest test.py
- name: build - name: build-app
image: docker:stable-dind image: banzaicloud/drone-kaniko
volumes: settings:
- name: dockersock registry: registry.kmlabz.com
path: /var/run repo: goldenpogacsa/${DRONE_REPO_NAME}
environment: username:
DOCKER_USERNAME: from_secret: DOCKER_USERNAME
from_secret: DOCKER_USERNAME password:
DOCKER_PASSWORD: from_secret: DOCKER_PASSWORD
from_secret: DOCKER_PASSWORD tags:
commands: - latest
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin - ${DRONE_BUILD_NUMBER}
- docker build -t="$DOCKER_USERNAME/producer" .
- docker build -t="$DOCKER_USERNAME/producer:$DRONE_BUILD_NUMBER" .
- docker push "$DOCKER_USERNAME/producer"
- docker push "$DOCKER_USERNAME/producer:$DRONE_BUILD_NUMBER"
- name: make_docs - name: make_docs
image: python:3.8 image: python:3.8
@ -56,11 +54,11 @@ steps:
from_secret: DOCKER_PASSWORD from_secret: DOCKER_PASSWORD
commands: commands:
- cd docs - cd docs
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin registry.kmlabz.com
- docker build -t="$DOCKER_USERNAME/producer-docs" . - docker build -t="registry.kmlabz.com/goldenpogacsa/producer-docs" .
- docker build -t="$DOCKER_USERNAME/producer-docs:$DRONE_BUILD_NUMBER" . - docker build -t="registry.kmlabz.com/goldenpogacsa/producer-docs:$DRONE_BUILD_NUMBER" .
- docker push "$DOCKER_USERNAME/producer-docs" - docker push "registry.kmlabz.com/goldenpogacsa/producer-docs"
- docker push "$DOCKER_USERNAME/producer-docs:$DRONE_BUILD_NUMBER" - docker push "registry.kmlabz.com/goldenpogacsa/producer-docs:$DRONE_BUILD_NUMBER"
- name: slack - name: slack
image: plugins/slack image: plugins/slack
@ -79,6 +77,8 @@ services:
volumes: volumes:
- name: dockersock - name: dockersock
path: /var/run path: /var/run
- name: cache
image: redis
volumes: volumes:
- name: dockersock - name: dockersock

View File

@ -2,6 +2,13 @@
P2P Producer P2P Producer
============ ============
This repository contains the Producer part of the project. The module manages the list of consumers and
sends data to the currently active one. If that becomes unavailable, it chooses an other one to the data to.
Implementation is done in python, the code is put into Docker images (as the consumers). To run the full project
clone the main repository and run
*docker-compose up.*
Produced by GoldenPogácsa Inc. Produced by GoldenPogácsa Inc.
TODO TODO

2
app.py
View File

@ -43,9 +43,9 @@ if __name__ == "__main__":
messagesender = MessageSender(communicator=communicator, uuid=generateduuid) messagesender = MessageSender(communicator=communicator, uuid=generateduuid)
consumerlocator = ConsumerLocator(uuid=generateduuid, communicator=communicator, consumerlocator = ConsumerLocator(uuid=generateduuid, communicator=communicator,
redisconnector=RedisConnector()) redisconnector=RedisConnector())
consumerlocator.learnconsumerlist()
while True: while True:
consumerlocator.learnconsumerlist()
LOGGER.info(f"Updating consumer list of {generateduuid}") LOGGER.info(f"Updating consumer list of {generateduuid}")
consumerlocator.updateconsumer() consumerlocator.updateconsumer()
LOGGER.info("Sending message to consumer") LOGGER.info("Sending message to consumer")

View File

@ -6,6 +6,7 @@ Communicator module
import logging import logging
import requests import requests
import requests.exceptions
__author__ = "@tormakris" __author__ = "@tormakris"
__copyright__ = "Copyright 2020, GoldenPogácsa Team" __copyright__ = "Copyright 2020, GoldenPogácsa Team"
@ -15,6 +16,7 @@ __version__text__ = "1"
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
class Communicator: class Communicator:
""" """
Class handling low level communication with consumers. Class handling low level communication with consumers.
@ -38,8 +40,11 @@ class Communicator:
""" """
currentconsumer=self.currenctconsumer currentconsumer=self.currenctconsumer
LOGGER.info(f"Sending message to {currentconsumer}") LOGGER.info(f"Sending message to {currentconsumer}")
postresponse=requests.post(f'http://{currentconsumer}/log', json={'uuid': self.uuid, 'message': message}) try:
LOGGER.debug(f"Message status code is:{postresponse.status_code}") postresponse=requests.post(f'http://{currentconsumer}/log', json={'uuid': self.uuid, 'message': message}, timeout=5)
LOGGER.debug(f"Message status code is:{postresponse.status_code}")
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
LOGGER.exception(e) # Fun fact: ez azt jelenti, hogy elveszett az üzenet... ide valami retry kellene inkább más consumerek felé...
def discoveravailableconsumers(self) -> list: def discoveravailableconsumers(self) -> list:
"""Get the list of available consumer from the current primary consumer. Logs the received list. """Get the list of available consumer from the current primary consumer. Logs the received list.
@ -48,12 +53,13 @@ class Communicator:
""" """
try: try:
currentconsumer = self.currenctconsumer currentconsumer = self.currenctconsumer
response = requests.get(f'http://{currentconsumer}/consumers') response = requests.get(f'http://{currentconsumer}/consumers', timeout=5)
json = response.json() json = response.json()
LOGGER.info(f"List of currently available consumers: {json}") LOGGER.info(f"List of currently available consumers: {json}")
return json return json
except Exception as e: except Exception as e:
LOGGER.exception(e) LOGGER.error("Could not query available consumer list!")
#LOGGER.exception(e)
return [] return []
def isconsumeravailable(self) -> bool: def isconsumeravailable(self) -> bool:
@ -63,10 +69,10 @@ class Communicator:
""" """
currentconsumer = self.currenctconsumer currentconsumer = self.currenctconsumer
try: try:
response = requests.get(f'http://{currentconsumer}/consumers') response = requests.get(f'http://{currentconsumer}/consumers', timeout=5)
isavailable = response.status_code == 200 isavailable = response.status_code == 200
except Exception as e: except Exception as e:
LOGGER.exception(e) #LOGGER.exception(e)
isavailable = False isavailable = False
LOGGER.info(f"Current consumer availability: {isavailable}") LOGGER.info(f"Current consumer availability: {isavailable}")
return isavailable return isavailable
@ -78,10 +84,10 @@ class Communicator:
:return: True if available, False otherwise :return: True if available, False otherwise
""" """
try: try:
response = requests.get(f'http://{consumer}/consumers') response = requests.get(f'http://{consumer}/consumers', timeout=5)
isavailable = response.status_code == 200 isavailable = response.status_code == 200
except Exception as e: except Exception as e:
LOGGER.exception(e) #LOGGER.exception(e)
isavailable = False isavailable = False
LOGGER.info(f"Consumer {consumer} availability: {isavailable}") LOGGER.info(f"Consumer {consumer} availability: {isavailable}")
return isavailable return isavailable

View File

@ -8,6 +8,7 @@ import datetime
from communicator import Communicator from communicator import Communicator
import os import os
from redisconnector import RedisConnector from redisconnector import RedisConnector
import logging
__author__ = "@dscharnitzky" __author__ = "@dscharnitzky"
__copyright__ = "Copyright 2020, GoldenPogácsa Team" __copyright__ = "Copyright 2020, GoldenPogácsa Team"
@ -15,6 +16,7 @@ __module_name__ = "consumerlocator"
__version__text__ = "1" __version__text__ = "1"
KNOWNCONSUMER = os.getenv("PRODUCER_KNOWNCONSUMER", '10.69.42.1') KNOWNCONSUMER = os.getenv("PRODUCER_KNOWNCONSUMER", '10.69.42.1')
LOGGER = logging.getLogger(__name__)
class ConsumerLocator: class ConsumerLocator:
@ -33,7 +35,7 @@ class ConsumerLocator:
:param communicator: the :class:'communicator.Communicator' instance that will be used for the low level communication. :param communicator: the :class:'communicator.Communicator' instance that will be used for the low level communication.
""" """
self.red = redisconnector self.red = redisconnector
self.red.consumerlist = [{"Host": KNOWNCONSUMER, "State": True, "LastOk": datetime.datetime.now()}] self.red.consumerlist = [{"Host": KNOWNCONSUMER, "State": True, "LastOk": datetime.datetime.now().timestamp()}]
self.red.currentconsumer = self.red.consumerlist[0] self.red.currentconsumer = self.red.consumerlist[0]
self.communicator = communicator self.communicator = communicator
@ -46,8 +48,12 @@ class ConsumerLocator:
:returns: None :returns: None
""" """
recievedconsumerlist = self.communicator.discoveravailableconsumers() recievedconsumerlist = self.communicator.discoveravailableconsumers()
if recievedconsumerlist is None: if not recievedconsumerlist:
return return
consumer_list = self.red.consumerlist
for recconsumer in recievedconsumerlist: for recconsumer in recievedconsumerlist:
contains = False contains = False
for consumer in self.red.consumerlist: for consumer in self.red.consumerlist:
@ -55,8 +61,10 @@ class ConsumerLocator:
contains = True contains = True
if not contains: if not contains:
self.red.consumerlist.append({"Host": recconsumer, "State": True, "LastOk": datetime.datetime.now()}) LOGGER.info(f"Learned about new consumer at {recconsumer}")
consumer_list.append({"Host": recconsumer, "State": True, "LastOk": datetime.datetime.now().timestamp()})
self.red.consumerlist = consumer_list
self.updateconsumerlist() self.updateconsumerlist()
def updateconsumerlist(self) -> None: def updateconsumerlist(self) -> None:
@ -68,16 +76,20 @@ class ConsumerLocator:
:return: None :return: None
""" """
removelist = [] removelist = []
for consumer in self.red.consumerlist: consumer_list = self.red.consumerlist
for consumer in consumer_list:
if not self.communicator.checkconsumer(consumer["Host"]): if not self.communicator.checkconsumer(consumer["Host"]):
consumer["State"] = False consumer["State"] = False
if datetime.datetime.now() - consumer["LastOk"] > datetime.timedelta(hours=1): if datetime.datetime.now() - datetime.datetime.fromtimestamp(consumer["LastOk"]) > datetime.timedelta(hours=1):
removelist.append(consumer) removelist.append(consumer)
else: else:
consumer["LastOk"] = datetime.datetime.now() consumer["LastOk"] = datetime.datetime.now().timestamp()
consumer["State"] = True consumer["State"] = True
for rem in removelist: for rem in removelist:
self.red.consumerlist.remove(rem) consumer_list.remove(rem)
self.red.consumerlist = consumer_list
def updateconsumer(self): def updateconsumer(self):
"""If the current consumer is not available, checks all the consumers in the list and updates the current one. """If the current consumer is not available, checks all the consumers in the list and updates the current one.
@ -100,6 +112,7 @@ class ConsumerLocator:
self.red.currentconsumer = newcurrentconsumer self.red.currentconsumer = newcurrentconsumer
if self.red.currentconsumer is not None: if self.red.currentconsumer is not None:
LOGGER.warning(f"Falling back to consumer at {newcurrentconsumer['Host']}")
self.learnconsumerlist() self.learnconsumerlist()
if self.red.currentconsumer is not None: if self.red.currentconsumer is not None:

View File

@ -48,7 +48,7 @@ class RedisConnector:
Gets currently active consumer. Gets currently active consumer.
:return: :return:
""" """
return self.redisconnection.get('currentConsumer') return json.loads(self.redisconnection.get('currentConsumer'))
def set_currentconsumer(self, currentconsumer): def set_currentconsumer(self, currentconsumer):
""" """
@ -56,7 +56,8 @@ class RedisConnector:
:param currentconsumer: :param currentconsumer:
:return: :return:
""" """
self.redisconnection.set('currentConsumer', currentconsumer) json_dict = json.dumps(currentconsumer)
self.redisconnection.set('currentConsumer', json_dict)
consumerlist = property(get_consumerlist, set_consumerlist) consumerlist = property(get_consumerlist, set_consumerlist)
currentconsumer = property(get_currentconsumer, set_currentconsumer) currentconsumer = property(get_currentconsumer, set_currentconsumer)

View File

@ -3,4 +3,5 @@ requests
pytest pytest
pytest-mock pytest-mock
pytest-httpserver pytest-httpserver
pytest-redis
redis redis

View File

@ -9,7 +9,7 @@ import consumerlocator
import communicator import communicator
import messagesender import messagesender
import redisconnector import redisconnector
from pytest_redis import factories
__author__ = "@tormakris" __author__ = "@tormakris"
__copyright__ = "Copyright 2020, GoldenPogácsa Team" __copyright__ = "Copyright 2020, GoldenPogácsa Team"
@ -17,6 +17,8 @@ __module_name__ = "test"
__version__text__ = "1" __version__text__ = "1"
generateduuid = 'c959ad81-58f9-4445-aab4-8f3d68aee1ad' generateduuid = 'c959ad81-58f9-4445-aab4-8f3d68aee1ad'
redis_proc = factories.redis_proc(host='cache', port=6379)
redis_db = factories.redisdb('redis_nooproc')
def test_generate_string(mocker): def test_generate_string(mocker):
@ -263,10 +265,11 @@ def test_updateconsumer(httpserver):
currentconsumer=f"127.0.0.1:{port}", currentconsumer=f"127.0.0.1:{port}",
uuid=generateduuid) uuid=generateduuid)
consumerlocator.KNOWNCONSUMER = f"127.0.0.1:{port}" consumerlocator.KNOWNCONSUMER = f"127.0.0.1:{port}"
redisconn = redisconnector.RedisConnector()
locator = consumerlocator.ConsumerLocator( locator = consumerlocator.ConsumerLocator(
uuid=generateduuid, communicator=comm, uuid=generateduuid, communicator=comm,
redisconnector=redisconnector.RedisConnector()) redisconnector=redisconn)
assert locator.currentconsumer is not None assert redisconn.currentconsumer is not None
ret = locator.updateconsumer() ret = locator.updateconsumer()
assert ret == f"127.0.0.1:{port}" assert ret == f"127.0.0.1:{port}"