Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Only do an update broadcast if relevant info has changed #11258

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions kolibri/core/discovery/test/test_network_broadcast.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ..utils.network.broadcast import LOCAL_DOMAIN
from ..utils.network.broadcast import LOCAL_EVENTS
from ..utils.network.broadcast import NETWORK_EVENTS
from ..utils.network.broadcast import SERVICE_TTL
from ..utils.network.broadcast import SERVICE_TYPE

MOCK_INTERFACE_IP = "111.222.111.222"
Expand Down Expand Up @@ -553,6 +554,193 @@ def test_update_service__is_self(
mock_logger.assert_not_called()
self.listener.mock.update_instance.assert_not_called()

@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._build_instance")
@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._get_service_info")
@mock.patch(BROADCAST_MODULE + "logger.info")
def test_update_service__no_change_less_than_TTL(
self, mock_logger, mock_get_service_info, mock_build_instance
):
service_info = mock.Mock(spec_set=ServiceInfo)("test")
mock_get_service_info.return_value = service_info
original_instance = KolibriInstance(
MOCK_ID, ip=MOCK_INTERFACE_IP, port=MOCK_PORT
)
expected_instance = KolibriInstance(
MOCK_ID, ip=MOCK_INTERFACE_IP, port=MOCK_PORT
)
mock_build_instance.return_value = expected_instance
self.broadcast.other_instances["test"] = original_instance
self.broadcast.update_service("test")
self.assertEqual(expected_instance, self.broadcast.other_instances["test"])
mock_get_service_info.assert_called_once_with("test")
mock_build_instance.assert_called_once_with(service_info)
mock_logger.assert_not_called()
self.listener.mock.update_instance.assert_not_called()

@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._build_instance")
@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._get_service_info")
@mock.patch(BROADCAST_MODULE + "logger.info")
def test_update_service__no_change_more_than_or_equal_to_TTL(
self, mock_logger, mock_get_service_info, mock_build_instance
):
service_info = mock.Mock(spec_set=ServiceInfo)("test")
mock_get_service_info.return_value = service_info
original_instance = KolibriInstance(
MOCK_ID, ip=MOCK_INTERFACE_IP, port=MOCK_PORT
)
expected_instance = KolibriInstance(
MOCK_ID, ip=MOCK_INTERFACE_IP, port=MOCK_PORT
)
expected_instance.last_seen = original_instance.last_seen + SERVICE_TTL
mock_build_instance.return_value = expected_instance
self.broadcast.other_instances["test"] = original_instance
self.broadcast.update_service("test")
self.assertEqual(expected_instance, self.broadcast.other_instances["test"])
mock_get_service_info.assert_called_once_with("test")
mock_build_instance.assert_called_once_with(service_info)
mock_logger.assert_called_once()
self.listener.mock.update_instance.assert_called_once_with(expected_instance)

@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._build_instance")
@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._get_service_info")
@mock.patch(BROADCAST_MODULE + "logger.info")
def test_update_service__change_id(
self, mock_logger, mock_get_service_info, mock_build_instance
):
service_info = mock.Mock(spec_set=ServiceInfo)("test")
mock_get_service_info.return_value = service_info
original_instance = KolibriInstance(
"not the same id", ip=MOCK_INTERFACE_IP, port=MOCK_PORT
)
expected_instance = KolibriInstance(
MOCK_ID, ip=MOCK_INTERFACE_IP, port=MOCK_PORT
)
mock_build_instance.return_value = expected_instance
self.broadcast.other_instances["test"] = original_instance
self.broadcast.update_service("test")
self.assertEqual(expected_instance, self.broadcast.other_instances["test"])
mock_get_service_info.assert_called_once_with("test")
mock_build_instance.assert_called_once_with(service_info)
mock_logger.assert_called_once()
self.listener.mock.update_instance.assert_called_once_with(expected_instance)

@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._build_instance")
@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._get_service_info")
@mock.patch(BROADCAST_MODULE + "logger.info")
def test_update_service__change_ip(
self, mock_logger, mock_get_service_info, mock_build_instance
):
service_info = mock.Mock(spec_set=ServiceInfo)("test")
mock_get_service_info.return_value = service_info
original_instance = KolibriInstance(MOCK_ID, ip="211.211.16.1", port=MOCK_PORT)
expected_instance = KolibriInstance(
MOCK_ID, ip=MOCK_INTERFACE_IP, port=MOCK_PORT
)
mock_build_instance.return_value = expected_instance
self.broadcast.other_instances["test"] = original_instance
self.broadcast.update_service("test")
self.assertEqual(expected_instance, self.broadcast.other_instances["test"])
mock_get_service_info.assert_called_once_with("test")
mock_build_instance.assert_called_once_with(service_info)
mock_logger.assert_called_once()
self.listener.mock.update_instance.assert_called_once_with(expected_instance)

@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._build_instance")
@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._get_service_info")
@mock.patch(BROADCAST_MODULE + "logger.info")
def test_update_service__change_port(
self, mock_logger, mock_get_service_info, mock_build_instance
):
service_info = mock.Mock(spec_set=ServiceInfo)("test")
mock_get_service_info.return_value = service_info
original_instance = KolibriInstance(MOCK_ID, ip=MOCK_INTERFACE_IP, port="2121")
expected_instance = KolibriInstance(
MOCK_ID, ip=MOCK_INTERFACE_IP, port=MOCK_PORT
)
mock_build_instance.return_value = expected_instance
self.broadcast.other_instances["test"] = original_instance
self.broadcast.update_service("test")
self.assertEqual(expected_instance, self.broadcast.other_instances["test"])
mock_get_service_info.assert_called_once_with("test")
mock_build_instance.assert_called_once_with(service_info)
mock_logger.assert_called_once()
self.listener.mock.update_instance.assert_called_once_with(expected_instance)

@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._build_instance")
@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._get_service_info")
@mock.patch(BROADCAST_MODULE + "logger.info")
def test_update_service__change_host(
self, mock_logger, mock_get_service_info, mock_build_instance
):
service_info = mock.Mock(spec_set=ServiceInfo)("test")
mock_get_service_info.return_value = service_info
original_instance = KolibriInstance(
MOCK_ID, ip=MOCK_INTERFACE_IP, port=MOCK_PORT, host="http://test.com"
)
expected_instance = KolibriInstance(
MOCK_ID, ip=MOCK_INTERFACE_IP, port=MOCK_PORT, host="http://test2.com"
)
mock_build_instance.return_value = expected_instance
self.broadcast.other_instances["test"] = original_instance
self.broadcast.update_service("test")
self.assertEqual(expected_instance, self.broadcast.other_instances["test"])
mock_get_service_info.assert_called_once_with("test")
mock_build_instance.assert_called_once_with(service_info)
mock_logger.assert_called_once()
self.listener.mock.update_instance.assert_called_once_with(expected_instance)

@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._build_instance")
@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._get_service_info")
@mock.patch(BROADCAST_MODULE + "logger.info")
def test_update_service__change_device_info(
self, mock_logger, mock_get_service_info, mock_build_instance
):
service_info = mock.Mock(spec_set=ServiceInfo)("test")
mock_get_service_info.return_value = service_info
original_instance = KolibriInstance(
MOCK_ID,
ip=MOCK_INTERFACE_IP,
port=MOCK_PORT,
device_info={"kolibri_version": "0.15.12"},
)
expected_instance = KolibriInstance(
MOCK_ID,
ip=MOCK_INTERFACE_IP,
port=MOCK_PORT,
device_info={"kolibri_version": "0.16.0"},
)
mock_build_instance.return_value = expected_instance
self.broadcast.other_instances["test"] = original_instance
self.broadcast.update_service("test")
self.assertEqual(expected_instance, self.broadcast.other_instances["test"])
mock_get_service_info.assert_called_once_with("test")
mock_build_instance.assert_called_once_with(service_info)
mock_logger.assert_called_once()
self.listener.mock.update_instance.assert_called_once_with(expected_instance)

@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._build_instance")
@mock.patch(BROADCAST_MODULE + "KolibriBroadcast._get_service_info")
@mock.patch(BROADCAST_MODULE + "logger.info")
def test_update_service__change_prefix(
self, mock_logger, mock_get_service_info, mock_build_instance
):
service_info = mock.Mock(spec_set=ServiceInfo)("test")
mock_get_service_info.return_value = service_info
original_instance = KolibriInstance(
MOCK_ID, ip=MOCK_INTERFACE_IP, port=MOCK_PORT
)
expected_instance = KolibriInstance(
MOCK_ID, ip=MOCK_INTERFACE_IP, port=MOCK_PORT, prefix="/kolibri"
)
mock_build_instance.return_value = expected_instance
self.broadcast.other_instances["test"] = original_instance
self.broadcast.update_service("test")
self.assertEqual(expected_instance, self.broadcast.other_instances["test"])
mock_get_service_info.assert_called_once_with("test")
mock_build_instance.assert_called_once_with(service_info)
mock_logger.assert_called_once()
self.listener.mock.update_instance.assert_called_once_with(expected_instance)

@mock.patch(BROADCAST_MODULE + "logger.info")
def test_remove_service(self, mock_logger):
expected_instance = mock.Mock(spec_set=KolibriInstance)(
Expand Down
28 changes: 28 additions & 0 deletions kolibri/core/discovery/utils/network/broadcast.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import logging
import socket
import time
import uuid

from magicbus.base import Bus
Expand Down Expand Up @@ -80,6 +81,7 @@ class KolibriInstance(object):
"device_info",
"service_info",
"prefix",
"last_seen",
)

def __init__(
Expand All @@ -99,6 +101,22 @@ def __init__(
self.is_self = False
self.service_info = None
self.prefix = prefix
self.last_seen = time.time()

def __eq__(self, other):
if self.id != other.id:
return False
if self.ip != other.ip:
return False
if self.port != other.port:
return False
if self.host != other.host:
return False
if self.device_info != other.device_info:
return False
if self.prefix != other.prefix:
return False
return True

@property
def name(self):
Expand Down Expand Up @@ -580,6 +598,16 @@ def update_service(self, name):

instance = self._build_instance(service_info)
if not instance.is_self:
if name in self.other_instances:
# if we already have the instance in our cache, we should check to
# see if we actually have any updated information. If not, we can
# just ignore the update
current_instance = self.other_instances[name]
if (
current_instance == instance
and instance.last_seen - current_instance.last_seen < SERVICE_TTL
):
return
self.other_instances[name] = instance
logger.info(
"Kolibri instance '%s' updated zeroconf network; device info: %s"
Expand Down