Hello community,
here is the log from the commit of package python-zeroconf for openSUSE:Factory
checked in at 2020-03-09 14:18:07
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-zeroconf (Old)
and /work/SRC/openSUSE:Factory/.python-zeroconf.new.26092 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-zeroconf"
Mon Mar 9 14:18:07 2020 rev:11 rq:782905 version:0.24.5
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-zeroconf/python-zeroconf.changes
2019-12-29 15:50:14.227191001 +0100
+++
/work/SRC/openSUSE:Factory/.python-zeroconf.new.26092/python-zeroconf.changes
2020-03-09 14:18:12.730697576 +0100
@@ -1,0 +2,16 @@
+Mon Mar 9 10:57:37 UTC 2020 - [email protected]
+
+- version update to 0.24.5
+ * Fixed issues with shared records being used where they shouldn't be (TXT,
SRV, A records are
+ unique now), thanks to Matt Saxon
+ * Stopped unnecessarily excluding host-only interfaces from
InterfaceChoice.all as they don't
+ forbid multicast, thanks to Andreas Oberritter
+ * Fixed repr() of IPv6 DNSAddress, thanks to Aldo Hoeben
+ * Removed duplicate update messages sent to listeners, thanks to Matt Saxon
+ * Added support for cooperating responders, thanks to Matt Saxon
+ * Optimized handle_response cache check, thanks to J. Nick Koston
+ * Fixed memory leak in DNSCache, thanks to J. Nick Koston
+ * Fixed resetting TTL in DNSRecord.reset_ttl(), thanks to Matt Saxon
+ * Improved various DNS class' string representations, thanks to Jay Hogg
+
+-------------------------------------------------------------------
Old:
----
python-zeroconf-0.24.3.tar.gz
New:
----
python-zeroconf-0.24.5.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-zeroconf.spec ++++++
--- /var/tmp/diff_new_pack.Nv4IDI/_old 2020-03-09 14:18:13.702698216 +0100
+++ /var/tmp/diff_new_pack.Nv4IDI/_new 2020-03-09 14:18:13.706698220 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-zeroconf
#
-# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2020 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%define skip_python2 1
Name: python-zeroconf
-Version: 0.24.3
+Version: 0.24.5
Release: 0
Summary: Pure Python Multicast DNS Service Discovery Library
(Bonjour/Avahi compatible)
License: LGPL-2.0-only
++++++ python-zeroconf-0.24.3.tar.gz -> python-zeroconf-0.24.5.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.24.3/README.rst
new/python-zeroconf-0.24.5/README.rst
--- old/python-zeroconf-0.24.3/README.rst 2019-12-23 15:59:30.000000000
+0100
+++ new/python-zeroconf-0.24.5/README.rst 2020-03-08 00:39:22.000000000
+0100
@@ -134,6 +134,25 @@
Changelog
=========
+0.24.5
+------
+
+* Fixed issues with shared records being used where they shouldn't be (TXT,
SRV, A records are
+ unique now), thanks to Matt Saxon
+* Stopped unnecessarily excluding host-only interfaces from
InterfaceChoice.all as they don't
+ forbid multicast, thanks to Andreas Oberritter
+* Fixed repr() of IPv6 DNSAddress, thanks to Aldo Hoeben
+* Removed duplicate update messages sent to listeners, thanks to Matt Saxon
+* Added support for cooperating responders, thanks to Matt Saxon
+* Optimized handle_response cache check, thanks to J. Nick Koston
+* Fixed memory leak in DNSCache, thanks to J. Nick Koston
+
+0.24.4
+------
+
+* Fixed resetting TTL in DNSRecord.reset_ttl(), thanks to Matt Saxon
+* Improved various DNS class' string representations, thanks to Jay Hogg
+
0.24.3
------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.24.3/zeroconf/__init__.py
new/python-zeroconf-0.24.5/zeroconf/__init__.py
--- old/python-zeroconf-0.24.3/zeroconf/__init__.py 2019-12-23
15:59:30.000000000 +0100
+++ new/python-zeroconf-0.24.5/zeroconf/__init__.py 2020-03-08
00:39:22.000000000 +0100
@@ -42,7 +42,7 @@
__author__ = 'Paul Scott-Murphy, William McBrine'
__maintainer__ = 'Jakub Stasiak <[email protected]>'
-__version__ = '0.24.3'
+__version__ = '0.24.5'
__license__ = 'LGPL'
@@ -420,7 +420,7 @@
result += ","
result += self.name
if other is not None:
- result += ",%s]" % cast(Any, other)
+ result += "]=%s" % cast(Any, other)
else:
result += "]"
return result
@@ -502,6 +502,8 @@
another record."""
self.created = other.created
self.ttl = other.ttl
+ self._expiration_time = self.get_expiration_time(100)
+ self._stale_time = self.get_expiration_time(50)
def write(self, out: 'DNSOutgoing') -> None:
"""Abstract method"""
@@ -509,7 +511,7 @@
def to_string(self, other: Union[bytes, str]) -> str:
"""String representation with additional information"""
- arg = "%s/%s,%s" % (self.ttl,
self.get_remaining_ttl(current_time_millis()), cast(Any, other))
+ arg = "%s/%s,%s" % (self.ttl,
int(self.get_remaining_ttl(current_time_millis())), cast(Any, other))
return DNSEntry.entry_to_string(self, "record", arg)
@@ -538,9 +540,13 @@
def __repr__(self) -> str:
"""String representation"""
try:
- return str(socket.inet_ntoa(self.address))
+ return self.to_string(
+ socket.inet_ntop(
+ socket.AF_INET6 if _is_v6_address(self.address) else
socket.AF_INET, self.address
+ )
+ )
except Exception: # TODO stop catching all Exceptions
- return str(self.address)
+ return self.to_string(str(self.address))
class DNSHinfo(DNSRecord):
@@ -580,7 +586,7 @@
def __repr__(self) -> str:
"""String representation"""
- return self.cpu + " " + self.os
+ return self.to_string(self.cpu + " " + self.os)
class DNSPointer(DNSRecord):
@@ -880,6 +886,10 @@
init = 0
finished = 1
+ @staticmethod
+ def is_type_unique(type_: int) -> bool:
+ return type_ == _TYPE_TXT or type_ == _TYPE_SRV or type_ == _TYPE_A or
type_ == _TYPE_AAAA
+
def add_question(self, record: DNSQuestion) -> None:
"""Adds a question"""
self.questions.append(record)
@@ -892,6 +902,10 @@
def add_answer_at_time(self, record: Optional[DNSRecord], now:
Union[float, int]) -> None:
"""Adds an answer if it does not expire by a certain time"""
if record is not None:
+
+ if self.is_type_unique(record.type):
+ assert record.unique
+
if now == 0 or not record.is_expired(now):
self.answers.append((record, now))
@@ -935,6 +949,9 @@
o All address records (type "A" and "AAAA") named in the SRV rdata.
"""
+ if self.is_type_unique(record.type):
+ assert record.unique
+
self.additionals.append(record)
def pack(self, format_: Union[bytes, str], value: Any) -> None:
@@ -1119,6 +1136,11 @@
try:
list_ = self.cache[entry.key]
list_.remove(entry)
+ # If we remove the last entry in the list
+ # we remove the key from the dict in order
+ # to avoid leaking memory
+ if not list_:
+ del self.cache[entry.key]
except (KeyError, ValueError):
pass
@@ -1836,14 +1858,7 @@
def get_all_addresses() -> List[str]:
- return list(
- set(
- addr.ip
- for iface in ifaddr.get_adapters()
- for addr in iface.ips
- if addr.is_IPv4 and addr.network_prefix != 32 # Host only netmask
255.255.255.255
- )
- )
+ return list(set(addr.ip for iface in ifaddr.get_adapters() for addr in
iface.ips if addr.is_IPv4))
def get_all_addresses_v6() -> List[int]:
@@ -2195,18 +2210,24 @@
self.remove_service_listener(listener)
def register_service(
- self, info: ServiceInfo, ttl: Optional[int] = None, allow_name_change:
bool = False
+ self,
+ info: ServiceInfo,
+ ttl: Optional[int] = None,
+ allow_name_change: bool = False,
+ cooperating_responders: bool = False,
) -> None:
"""Registers service information to the network with a default TTL.
Zeroconf will then respond to requests for information for that
service. The name of the service may be changed if needed to make
- it unique on the network."""
+ it unique on the network. Additionally multiple cooperating responders
+ can register the same service on the network for resilience
+ (if you want this behavior set `cooperating_responders` to `True`)."""
if ttl is not None:
# ttl argument is used to maintain backward compatibility
# Setting TTLs via ServiceInfo is preferred
info.host_ttl = ttl
info.other_ttl = ttl
- self.check_service(info, allow_name_change)
+ self.check_service(info, allow_name_change, cooperating_responders)
self.services[info.name.lower()] = info
if info.type in self.servicetypes:
self.servicetypes[info.type] += 1
@@ -2242,7 +2263,7 @@
DNSService(
info.name,
_TYPE_SRV,
- _CLASS_IN,
+ _CLASS_IN | _CLASS_UNIQUE,
info.host_ttl,
info.priority,
info.weight,
@@ -2252,10 +2273,14 @@
0,
)
- out.add_answer_at_time(DNSText(info.name, _TYPE_TXT, _CLASS_IN,
info.other_ttl, info.text), 0)
+ out.add_answer_at_time(
+ DNSText(info.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE,
info.other_ttl, info.text), 0
+ )
for address in info.addresses_by_version(IPVersion.All):
type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A
- out.add_answer_at_time(DNSAddress(info.server, type_,
_CLASS_IN, info.host_ttl, address), 0)
+ out.add_answer_at_time(
+ DNSAddress(info.server, type_, _CLASS_IN | _CLASS_UNIQUE,
info.host_ttl, address), 0
+ )
self.send(out)
i += 1
next_time += _REGISTER_TIME
@@ -2284,7 +2309,7 @@
DNSService(
info.name,
_TYPE_SRV,
- _CLASS_IN,
+ _CLASS_IN | _CLASS_UNIQUE,
0,
info.priority,
info.weight,
@@ -2293,11 +2318,13 @@
),
0,
)
- out.add_answer_at_time(DNSText(info.name, _TYPE_TXT, _CLASS_IN, 0,
info.text), 0)
+ out.add_answer_at_time(DNSText(info.name, _TYPE_TXT, _CLASS_IN |
_CLASS_UNIQUE, 0, info.text), 0)
for address in info.addresses_by_version(IPVersion.All):
type_ = _TYPE_AAAA if _is_v6_address(address) else _TYPE_A
- out.add_answer_at_time(DNSAddress(info.server, type_,
_CLASS_IN, 0, address), 0)
+ out.add_answer_at_time(
+ DNSAddress(info.server, type_, _CLASS_IN | _CLASS_UNIQUE,
0, address), 0
+ )
self.send(out)
i += 1
next_time += _UNREGISTER_TIME
@@ -2320,7 +2347,7 @@
DNSService(
info.name,
_TYPE_SRV,
- _CLASS_IN,
+ _CLASS_IN | _CLASS_UNIQUE,
0,
info.priority,
info.weight,
@@ -2329,15 +2356,21 @@
),
0,
)
- out.add_answer_at_time(DNSText(info.name, _TYPE_TXT,
_CLASS_IN, 0, info.text), 0)
+ out.add_answer_at_time(
+ DNSText(info.name, _TYPE_TXT, _CLASS_IN |
_CLASS_UNIQUE, 0, info.text), 0
+ )
for address in info.addresses_by_version(IPVersion.All):
type_ = _TYPE_AAAA if _is_v6_address(address) else
_TYPE_A
- out.add_answer_at_time(DNSAddress(info.server, type_,
_CLASS_IN, 0, address), 0)
+ out.add_answer_at_time(
+ DNSAddress(info.server, type_, _CLASS_IN |
_CLASS_UNIQUE, 0, address), 0
+ )
self.send(out)
i += 1
next_time += _UNREGISTER_TIME
- def check_service(self, info: ServiceInfo, allow_name_change: bool) ->
None:
+ def check_service(
+ self, info: ServiceInfo, allow_name_change: bool,
cooperating_responders: bool = False
+ ) -> None:
"""Checks the network for a unique service name, modifying the
ServiceInfo passed in if it is not unique."""
@@ -2354,17 +2387,18 @@
next_time = now
i = 0
while i < 3:
- # check for a name conflict
- while self.cache.current_entry_with_name_and_alias(info.type,
info.name):
- if not allow_name_change:
- raise NonUniqueNameException
-
- # change the name and look for a conflict
- info.name = '%s-%s.%s' % (instance_name, next_instance_number,
info.type)
- next_instance_number += 1
- service_type_name(info.name)
- next_time = now
- i = 0
+ if not cooperating_responders:
+ # check for a name conflict
+ while self.cache.current_entry_with_name_and_alias(info.type,
info.name):
+ if not allow_name_change:
+ raise NonUniqueNameException
+
+ # change the name and look for a conflict
+ info.name = '%s-%s.%s' % (instance_name,
next_instance_number, info.type)
+ next_instance_number += 1
+ service_type_name(info.name)
+ next_time = now
+ i = 0
if now < next_time:
self.wait(next_time - now)
@@ -2411,9 +2445,25 @@
are held in the cache, and listeners are notified."""
now = current_time_millis()
for record in msg.answers:
+
+ updated = True
+
if record.unique: #
https://tools.ietf.org/html/rfc6762#section-10.2
- for entry in self.cache.entries():
- if DNSEntry.__eq__(entry, record) and (record.created -
entry.created > 1000):
+ # Since the cache format is keyed on the lower case record name
+ # we can avoid iterating everything in the cache and
+ # only look though entries for the specific name.
+ # entries_with_name will take care of converting to lowercase
+ #
+ # We make a copy of the list that entries_with_name returns
+ # since we cannot iterate over something we might remove
+ for entry in self.cache.entries_with_name(record.name).copy():
+
+ if entry == record:
+ updated = False
+
+ # Check the time first because it is far cheaper
+ # than the __eq__
+ if (record.created - entry.created > 1000) and
DNSEntry.__eq__(entry, record):
self.cache.remove(entry)
expired = record.is_expired(now)
@@ -2423,7 +2473,8 @@
maybe_entry.reset_ttl(record)
else:
self.cache.add(record)
- self.update_record(now, record)
+ if updated:
+ self.update_record(now, record)
else:
if maybe_entry is not None:
self.update_record(now, record)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-zeroconf-0.24.3/zeroconf/test.py
new/python-zeroconf-0.24.5/zeroconf/test.py
--- old/python-zeroconf-0.24.3/zeroconf/test.py 2019-12-23 15:59:30.000000000
+0100
+++ new/python-zeroconf-0.24.5/zeroconf/test.py 2020-03-08 00:39:22.000000000
+0100
@@ -63,7 +63,17 @@
def test_dns_address_repr(self):
address = r.DNSAddress('irrelevant', r._TYPE_SOA, r._CLASS_IN, 1, b'a')
- repr(address)
+ assert repr(address).endswith("b'a'")
+
+ address_ipv4 = r.DNSAddress(
+ 'irrelevant', r._TYPE_SOA, r._CLASS_IN, 1,
socket.inet_pton(socket.AF_INET, '127.0.0.1')
+ )
+ assert repr(address_ipv4).endswith('127.0.0.1')
+
+ address_ipv6 = r.DNSAddress(
+ 'irrelevant', r._TYPE_SOA, r._CLASS_IN, 1,
socket.inet_pton(socket.AF_INET6, '::1')
+ )
+ assert repr(address_ipv6).endswith('::1')
def test_dns_question_repr(self):
question = r.DNSQuestion('irrelevant', r._TYPE_SRV, r._CLASS_IN |
r._CLASS_UNIQUE)
@@ -79,6 +89,21 @@
self.assertRaises(r.AbstractMethodException, record.__eq__, record)
self.assertRaises(r.AbstractMethodException, record.write, None)
+ def test_dns_record_reset_ttl(self):
+ record = r.DNSRecord('irrelevant', r._TYPE_SRV, r._CLASS_IN,
r._DNS_HOST_TTL)
+ time.sleep(1)
+ record2 = r.DNSRecord('irrelevant', r._TYPE_SRV, r._CLASS_IN,
r._DNS_HOST_TTL)
+ now = r.current_time_millis()
+
+ assert record.created != record2.created
+ assert record.get_remaining_ttl(now) != record2.get_remaining_ttl(now)
+
+ record.reset_ttl(record2)
+
+ assert record.ttl == record2.ttl
+ assert record.created == record2.created
+ assert record.get_remaining_ttl(now) == record2.get_remaining_ttl(now)
+
def test_service_info_dunder(self):
type_ = "_test-srvc-type._tcp.local."
name = "xxxyyy"
@@ -131,7 +156,17 @@
def test_parse_own_packet_response(self):
generated = r.DNSOutgoing(r._FLAGS_QR_RESPONSE)
generated.add_answer_at_time(
- r.DNSService("æøå.local.", r._TYPE_SRV, r._CLASS_IN,
r._DNS_HOST_TTL, 0, 0, 80, "foo.local."), 0
+ r.DNSService(
+ "æøå.local.",
+ r._TYPE_SRV,
+ r._CLASS_IN | r._CLASS_UNIQUE,
+ r._DNS_HOST_TTL,
+ 0,
+ 0,
+ 80,
+ "foo.local.",
+ ),
+ 0,
)
parsed = r.DNSIncoming(generated.packet())
self.assertEqual(len(generated.answers), 1)
@@ -151,13 +186,34 @@
question = r.DNSQuestion("testname.local.", r._TYPE_SRV, r._CLASS_IN)
query_generated.add_question(question)
answer1 = r.DNSService(
- "testname1.local.", r._TYPE_SRV, r._CLASS_IN, r._DNS_HOST_TTL, 0,
0, 80, "foo.local."
+ "testname1.local.",
+ r._TYPE_SRV,
+ r._CLASS_IN | r._CLASS_UNIQUE,
+ r._DNS_HOST_TTL,
+ 0,
+ 0,
+ 80,
+ "foo.local.",
)
staleanswer2 = r.DNSService(
- "testname2.local.", r._TYPE_SRV, r._CLASS_IN, r._DNS_HOST_TTL / 2,
0, 0, 80, "foo.local."
+ "testname2.local.",
+ r._TYPE_SRV,
+ r._CLASS_IN | r._CLASS_UNIQUE,
+ r._DNS_HOST_TTL / 2,
+ 0,
+ 0,
+ 80,
+ "foo.local.",
)
answer2 = r.DNSService(
- "testname2.local.", r._TYPE_SRV, r._CLASS_IN, r._DNS_HOST_TTL, 0,
0, 80, "foo.local."
+ "testname2.local.",
+ r._TYPE_SRV,
+ r._CLASS_IN | r._CLASS_UNIQUE,
+ r._DNS_HOST_TTL,
+ 0,
+ 0,
+ 80,
+ "foo.local.",
)
query_generated.add_answer_at_time(answer1, 0)
query_generated.add_answer_at_time(staleanswer2, 0)
@@ -407,6 +463,9 @@
# verify name conflict
self.assertRaises(r.NonUniqueNameException, zc.register_service,
info_service)
+ # verify no name conflict
https://tools.ietf.org/html/rfc6762#section-6.6
+ zc.register_service(info_service, cooperating_responders=True)
+
zc.register_service(info_service, allow_name_change=True)
assert info_service.name.split('.')[0] == '%s-%d' % (name,
number_hosts + 1)
@@ -429,7 +488,8 @@
out = r.DNSOutgoing(r._FLAGS_QR_RESPONSE | r._FLAGS_AA)
out.add_answer_at_time(r.DNSPointer(type_, r._TYPE_PTR, r._CLASS_IN,
r._DNS_OTHER_TTL, name), 0)
out.add_answer_at_time(
- r.DNSService(type_, r._TYPE_SRV, r._CLASS_IN, r._DNS_HOST_TTL, 0,
0, 80, name), 0
+ r.DNSService(type_, r._TYPE_SRV, r._CLASS_IN | r._CLASS_UNIQUE,
r._DNS_HOST_TTL, 0, 0, 80, name),
+ 0,
)
zc.send(out)
@@ -472,7 +532,7 @@
ttl = 0
generated.add_answer_at_time(
- r.DNSPointer(service_type, r._TYPE_PTR, r._CLASS_IN |
r._CLASS_UNIQUE, ttl, service_name), 0
+ r.DNSPointer(service_type, r._TYPE_PTR, r._CLASS_IN, ttl,
service_name), 0
)
generated.add_answer_at_time(
r.DNSService(
@@ -655,7 +715,7 @@
addr = "2606:2800:220:1:248:1893:25c8:1946" # example.com
packed = socket.inet_pton(socket.AF_INET6, addr)
generated = r.DNSOutgoing(0)
- answer = r.DNSAddress('domain', r._TYPE_AAAA, r._CLASS_IN, 1, packed)
+ answer = r.DNSAddress('domain', r._TYPE_AAAA, r._CLASS_IN |
r._CLASS_UNIQUE, 1, packed)
generated.add_additional_answer(answer)
packet = generated.packet()
parsed = r.DNSIncoming(packet)
@@ -768,6 +828,17 @@
cached_record = cache.get(entry)
self.assertEqual(cached_record, record2)
+ def test_cache_empty_does_not_leak_memory_by_leaving_empty_list(self):
+ record1 = r.DNSAddress('a', r._TYPE_SOA, r._CLASS_IN, 1, b'a')
+ record2 = r.DNSAddress('a', r._TYPE_SOA, r._CLASS_IN, 1, b'b')
+ cache = r.DNSCache()
+ cache.add(record1)
+ cache.add(record2)
+ assert 'a' in cache.cache
+ cache.remove(record1)
+ cache.remove(record2)
+ assert 'a' not in cache.cache
+
class ServiceTypesQuery(unittest.TestCase):
def test_integration_with_listener(self):
@@ -871,6 +942,7 @@
service_added = Event()
service_removed = Event()
service_updated = Event()
+ service_updated2 = Event()
subtype_name = "My special Subtype"
type_ = "_http._tcp.local."
@@ -887,7 +959,7 @@
service_removed.set()
def update_service(self, zeroconf, type, name):
- pass
+ service_updated2.set()
class MySubListener(r.ServiceListener):
def add_service(self, zeroconf, type, name):
@@ -951,7 +1023,7 @@
assert info is not None
assert info.properties[b'prop_none'] is False
- # Begin material test addition
+ # test TXT record update
sublistener = MySubListener()
zeroconf_browser.add_service_listener(registration_name,
sublistener)
properties['prop_blank'] = b'an updated string'
@@ -966,7 +1038,6 @@
info = zeroconf_browser.get_service_info(type_, registration_name)
assert info is not None
assert info.properties[b'prop_blank'] == properties['prop_blank']
- # End material test addition
zeroconf_registrar.unregister_service(info_service)
service_removed.wait(1)
@@ -1028,7 +1099,7 @@
ttl = 0
generated.add_answer_at_time(
- r.DNSPointer(service_type, r._TYPE_PTR, r._CLASS_IN |
r._CLASS_UNIQUE, ttl, service_name), 0
+ r.DNSPointer(service_type, r._TYPE_PTR, r._CLASS_IN, ttl,
service_name), 0
)
generated.add_answer_at_time(
r.DNSService(
@@ -1068,6 +1139,7 @@
service_updated_event.clear()
service_text = b'path=/~humingchun/'
zeroconf.handle_response(mock_incoming_msg(r.ServiceStateChange.Updated))
+
zeroconf.handle_response(mock_incoming_msg(r.ServiceStateChange.Updated))
service_updated_event.wait(1)
assert service_added is True
assert service_updated_count == 2