test_ssdp.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. # -*- coding: utf-8 -*-
  2. """
  3. :codeauthor: :email:`Bo Maryniuk <bo@suse.de>`
  4. """
  5. from __future__ import absolute_import, print_function, unicode_literals
  6. import datetime
  7. import salt.utils.ssdp as ssdp
  8. import salt.utils.stringutils
  9. from salt.ext import six
  10. from salt.ext.six.moves import zip
  11. from tests.support.mock import MagicMock, patch
  12. from tests.support.unit import TestCase, skipIf
  13. try:
  14. import pytest
  15. except ImportError:
  16. pytest = None
  17. class Mocks(object):
  18. def get_socket_mock(self, expected_ip, expected_hostname):
  19. """
  20. Get a mock of a socket
  21. :return:
  22. """
  23. sck = MagicMock()
  24. sck.getsockname = MagicMock(return_value=(expected_ip, 123456))
  25. sock_mock = MagicMock()
  26. sock_mock.socket = MagicMock(return_value=sck)
  27. sock_mock.gethostname = MagicMock(return_value=expected_hostname)
  28. sock_mock.gethostbyname = MagicMock(return_value=expected_ip)
  29. return sock_mock
  30. def get_ssdp_factory(self, expected_ip=None, expected_hostname=None, **config):
  31. if expected_ip is None:
  32. expected_ip = "127.0.0.1"
  33. if expected_hostname is None:
  34. expected_hostname = "localhost"
  35. sock_mock = self.get_socket_mock(expected_ip, expected_hostname)
  36. with patch("salt.utils.ssdp.socket", sock_mock):
  37. factory = ssdp.SSDPFactory(**config)
  38. return factory
  39. def get_ssdp_discovery_client(
  40. self, expected_ip=None, expected_hostname=None, **config
  41. ):
  42. if expected_ip is None:
  43. expected_ip = "127.0.0.1"
  44. if expected_hostname is None:
  45. expected_hostname = "localhost"
  46. sock_mock = self.get_socket_mock(expected_ip, expected_hostname)
  47. with patch("salt.utils.ssdp.socket", sock_mock):
  48. factory = ssdp.SSDPDiscoveryClient(**config)
  49. return factory
  50. def get_ssdp_discovery_server(
  51. self, expected_ip=None, expected_hostname=None, **config
  52. ):
  53. if expected_ip is None:
  54. expected_ip = "127.0.0.1"
  55. if expected_hostname is None:
  56. expected_hostname = "localhost"
  57. sock_mock = self.get_socket_mock(expected_ip, expected_hostname)
  58. with patch("salt.utils.ssdp.socket", sock_mock):
  59. factory = ssdp.SSDPDiscoveryServer(**config)
  60. return factory
  61. @skipIf(pytest is None, "PyTest is missing")
  62. class SSDPBaseTestCase(TestCase, Mocks):
  63. """
  64. TestCase for SSDP-related parts.
  65. """
  66. @staticmethod
  67. def exception_generic(*args, **kwargs):
  68. """
  69. Side effect
  70. :return:
  71. """
  72. raise Exception("some network error")
  73. @staticmethod
  74. def exception_attr_error(*args, **kwargs):
  75. """
  76. Side effect
  77. :return:
  78. """
  79. raise AttributeError("attribute error: {0}. {1}".format(args, kwargs))
  80. @patch("salt.utils.ssdp._json", None)
  81. @patch("salt.utils.ssdp.asyncio", None)
  82. def test_base_avail(self):
  83. """
  84. Test SSDP base class availability method.
  85. :return:
  86. """
  87. base = ssdp.SSDPBase()
  88. assert not base._is_available()
  89. with patch("salt.utils.ssdp._json", True):
  90. assert not base._is_available()
  91. with patch("salt.utils.ssdp.asyncio", True):
  92. assert not base._is_available()
  93. with patch("salt.utils.ssdp._json", True), patch(
  94. "salt.utils.ssdp.asyncio", True
  95. ):
  96. assert base._is_available()
  97. def test_base_protocol_settings(self):
  98. """
  99. Tests default constants data.
  100. :return:
  101. """
  102. base = ssdp.SSDPBase()
  103. v_keys = ["signature", "answer", "port", "listen_ip", "timeout"]
  104. v_vals = ["__salt_master_service", {}, 4520, "0.0.0.0", 3]
  105. for key in v_keys:
  106. assert key in base.DEFAULTS
  107. for key in base.DEFAULTS:
  108. assert key in v_keys
  109. for key, value in zip(v_keys, v_vals):
  110. assert base.DEFAULTS[key] == value
  111. def test_base_self_ip(self):
  112. """
  113. Test getting self IP method.
  114. :return:
  115. """
  116. base = ssdp.SSDPBase()
  117. expected_ip = "192.168.1.10"
  118. expected_host = "oxygen"
  119. sock_mock = self.get_socket_mock(expected_ip, expected_host)
  120. with patch("salt.utils.ssdp.socket", sock_mock):
  121. assert base.get_self_ip() == expected_ip
  122. sock_mock.socket().getsockname.side_effect = SSDPBaseTestCase.exception_generic
  123. with patch("salt.utils.ssdp.socket", sock_mock):
  124. assert base.get_self_ip() == expected_ip
  125. @skipIf(pytest is None, "PyTest is missing")
  126. class SSDPFactoryTestCase(TestCase, Mocks):
  127. """
  128. Test socket protocol
  129. """
  130. def test_attr_check(self):
  131. """
  132. Tests attributes are set to the base class
  133. :return:
  134. """
  135. config = {
  136. ssdp.SSDPBase.SIGNATURE: "-signature-",
  137. ssdp.SSDPBase.ANSWER: {"this-is": "the-answer"},
  138. }
  139. expected_ip = "10.10.10.10"
  140. factory = self.get_ssdp_factory(expected_ip=expected_ip, **config)
  141. for attr in [ssdp.SSDPBase.SIGNATURE, ssdp.SSDPBase.ANSWER]:
  142. assert hasattr(factory, attr)
  143. assert getattr(factory, attr) == config[attr]
  144. assert not factory.disable_hidden
  145. assert factory.my_ip == expected_ip
  146. def test_transport_sendto_success(self):
  147. """
  148. Test transport send_to.
  149. :return:
  150. """
  151. transport = MagicMock()
  152. log = MagicMock()
  153. factory = self.get_ssdp_factory()
  154. with patch.object(factory, "transport", transport), patch.object(
  155. factory, "log", log
  156. ):
  157. data = {"some": "data"}
  158. addr = "10.10.10.10"
  159. factory._sendto(data=data, addr=addr)
  160. assert factory.transport.sendto.called
  161. assert factory.transport.sendto.mock_calls[0][1][0]["some"] == "data"
  162. assert factory.transport.sendto.mock_calls[0][2]["addr"] == "10.10.10.10"
  163. assert factory.log.debug.called
  164. assert factory.log.debug.mock_calls[0][1][0] == "Sent successfully"
  165. def test_transport_sendto_retry(self):
  166. """
  167. Test transport send_to.
  168. :return:
  169. """
  170. with patch("salt.utils.ssdp.time.sleep", MagicMock()):
  171. transport = MagicMock()
  172. transport.sendto = MagicMock(
  173. side_effect=SSDPBaseTestCase.exception_attr_error
  174. )
  175. log = MagicMock()
  176. factory = self.get_ssdp_factory()
  177. with patch.object(factory, "transport", transport), patch.object(
  178. factory, "log", log
  179. ):
  180. data = {"some": "data"}
  181. addr = "10.10.10.10"
  182. factory._sendto(data=data, addr=addr)
  183. assert factory.transport.sendto.called
  184. assert ssdp.time.sleep.called
  185. assert (
  186. ssdp.time.sleep.call_args[0][0] > 0
  187. and ssdp.time.sleep.call_args[0][0] < 0.5
  188. )
  189. assert factory.log.debug.called
  190. assert "Permission error" in factory.log.debug.mock_calls[0][1][0]
  191. def test_datagram_signature_bad(self):
  192. """
  193. Test datagram_received on bad signature
  194. :return:
  195. """
  196. factory = self.get_ssdp_factory()
  197. data = "nonsense"
  198. addr = "10.10.10.10", "foo.suse.de"
  199. with patch.object(factory, "log", MagicMock()):
  200. factory.datagram_received(data=data, addr=addr)
  201. assert factory.log.debug.called
  202. assert "Received bad signature from" in factory.log.debug.call_args[0][0]
  203. assert factory.log.debug.call_args[0][1] == addr[0]
  204. assert factory.log.debug.call_args[0][2] == addr[1]
  205. def test_datagram_signature_wrong_timestamp_quiet(self):
  206. """
  207. Test datagram receives a wrong timestamp (no reply).
  208. :return:
  209. """
  210. factory = self.get_ssdp_factory()
  211. data = "{}nonsense".format(ssdp.SSDPBase.DEFAULTS[ssdp.SSDPBase.SIGNATURE])
  212. addr = "10.10.10.10", "foo.suse.de"
  213. with patch.object(factory, "log", MagicMock()), patch.object(
  214. factory, "_sendto", MagicMock()
  215. ):
  216. factory.datagram_received(data=data, addr=addr)
  217. assert factory.log.debug.called
  218. assert (
  219. "Received invalid timestamp in package"
  220. in factory.log.debug.call_args[0][0]
  221. )
  222. assert not factory._sendto.called
  223. def test_datagram_signature_wrong_timestamp_reply(self):
  224. """
  225. Test datagram receives a wrong timestamp.
  226. :return:
  227. """
  228. factory = self.get_ssdp_factory()
  229. factory.disable_hidden = True
  230. signature = ssdp.SSDPBase.DEFAULTS[ssdp.SSDPBase.SIGNATURE]
  231. data = "{}nonsense".format(signature)
  232. addr = "10.10.10.10", "foo.suse.de"
  233. with patch.object(factory, "log", MagicMock()), patch.object(
  234. factory, "_sendto", MagicMock()
  235. ):
  236. factory.datagram_received(data=data, addr=addr)
  237. assert factory.log.debug.called
  238. assert (
  239. "Received invalid timestamp in package"
  240. in factory.log.debug.call_args[0][0]
  241. )
  242. assert factory._sendto.called
  243. assert (
  244. "{}:E:Invalid timestamp".format(signature)
  245. == factory._sendto.call_args[0][0]
  246. )
  247. def test_datagram_signature_outdated_timestamp_quiet(self):
  248. """
  249. Test if datagram processing reacts on outdated message (more than 20 seconds). Quiet mode.
  250. :return:
  251. """
  252. factory = self.get_ssdp_factory()
  253. signature = ssdp.SSDPBase.DEFAULTS[ssdp.SSDPBase.SIGNATURE]
  254. data = "{}{}".format(signature, "1516623820")
  255. addr = "10.10.10.10", "foo.suse.de"
  256. ahead_dt = datetime.datetime.fromtimestamp(1516623841)
  257. curnt_dt = datetime.datetime.fromtimestamp(1516623820)
  258. delta = datetime.timedelta(0, 20)
  259. with patch.object(factory, "log", MagicMock()), patch.object(
  260. factory, "_sendto"
  261. ), patch("salt.utils.ssdp.datetime.datetime", MagicMock()), patch(
  262. "salt.utils.ssdp.datetime.datetime.now", MagicMock(return_value=ahead_dt)
  263. ), patch(
  264. "salt.utils.ssdp.datetime.datetime.fromtimestamp",
  265. MagicMock(return_value=curnt_dt),
  266. ), patch(
  267. "salt.utils.ssdp.datetime.timedelta", MagicMock(return_value=delta)
  268. ):
  269. factory.datagram_received(data=data, addr=addr)
  270. assert factory.log.debug.called
  271. assert not factory.disable_hidden
  272. assert not factory._sendto.called
  273. assert "Received outdated package" in factory.log.debug.call_args[0][0]
  274. def test_datagram_signature_outdated_timestamp_reply(self):
  275. """
  276. Test if datagram processing reacts on outdated message (more than 20 seconds). Reply mode.
  277. :return:
  278. """
  279. factory = self.get_ssdp_factory()
  280. factory.disable_hidden = True
  281. signature = ssdp.SSDPBase.DEFAULTS[ssdp.SSDPBase.SIGNATURE]
  282. data = "{}{}".format(signature, "1516623820")
  283. addr = "10.10.10.10", "foo.suse.de"
  284. ahead_dt = datetime.datetime.fromtimestamp(1516623841)
  285. curnt_dt = datetime.datetime.fromtimestamp(1516623820)
  286. delta = datetime.timedelta(0, 20)
  287. with patch.object(factory, "log", MagicMock()), patch.object(
  288. factory, "_sendto"
  289. ), patch("salt.utils.ssdp.datetime.datetime", MagicMock()), patch(
  290. "salt.utils.ssdp.datetime.datetime.now", MagicMock(return_value=ahead_dt)
  291. ), patch(
  292. "salt.utils.ssdp.datetime.datetime.fromtimestamp",
  293. MagicMock(return_value=curnt_dt),
  294. ), patch(
  295. "salt.utils.ssdp.datetime.timedelta", MagicMock(return_value=delta)
  296. ):
  297. factory.datagram_received(data=data, addr=addr)
  298. assert factory.log.debug.called
  299. assert factory.disable_hidden
  300. assert factory._sendto.called
  301. assert factory._sendto.call_args[0][
  302. 0
  303. ] == "{}:E:Timestamp is too old".format(signature)
  304. assert "Received outdated package" in factory.log.debug.call_args[0][0]
  305. def test_datagram_signature_correct_timestamp_reply(self):
  306. """
  307. Test if datagram processing sends out correct reply within 20 seconds.
  308. :return:
  309. """
  310. factory = self.get_ssdp_factory()
  311. factory.disable_hidden = True
  312. signature = ssdp.SSDPBase.DEFAULTS[ssdp.SSDPBase.SIGNATURE]
  313. data = "{}{}".format(signature, "1516623820")
  314. addr = "10.10.10.10", "foo.suse.de"
  315. ahead_dt = datetime.datetime.fromtimestamp(1516623840)
  316. curnt_dt = datetime.datetime.fromtimestamp(1516623820)
  317. delta = datetime.timedelta(0, 20)
  318. with patch.object(factory, "log", MagicMock()), patch.object(
  319. factory, "_sendto"
  320. ), patch("salt.utils.ssdp.datetime.datetime", MagicMock()), patch(
  321. "salt.utils.ssdp.datetime.datetime.now", MagicMock(return_value=ahead_dt)
  322. ), patch(
  323. "salt.utils.ssdp.datetime.datetime.fromtimestamp",
  324. MagicMock(return_value=curnt_dt),
  325. ), patch(
  326. "salt.utils.ssdp.datetime.timedelta", MagicMock(return_value=delta)
  327. ):
  328. factory.datagram_received(data=data, addr=addr)
  329. assert factory.log.debug.called
  330. assert factory.disable_hidden
  331. assert factory._sendto.called
  332. assert factory._sendto.call_args[0][0] == salt.utils.stringutils.to_bytes(
  333. "{}:@:{{}}".format(signature)
  334. )
  335. assert 'Received "%s" from %s:%s' in factory.log.debug.call_args[0][0]
  336. @skipIf(pytest is None, "PyTest is missing")
  337. class SSDPServerTestCase(TestCase, Mocks):
  338. """
  339. Server-related test cases
  340. """
  341. def test_config_detached(self):
  342. """
  343. Test if configuration is not a reference.
  344. :return:
  345. """
  346. old_ip = "10.10.10.10"
  347. new_ip = "20.20.20.20"
  348. config = {"answer": {"master": old_ip}}
  349. with patch(
  350. "salt.utils.ssdp.SSDPDiscoveryServer.get_self_ip",
  351. MagicMock(return_value=new_ip),
  352. ):
  353. srv = ssdp.SSDPDiscoveryServer(**config)
  354. assert srv._config["answer"]["master"] == new_ip
  355. assert config["answer"]["master"] == old_ip
  356. def test_run(self):
  357. """
  358. Test server runner.
  359. :return:
  360. """
  361. with patch("salt.utils.ssdp.SSDPFactory", MagicMock()):
  362. config = {
  363. "answer": {"master": "10.10.10.10"},
  364. ssdp.SSDPBase.LISTEN_IP: "10.10.10.10",
  365. ssdp.SSDPBase.PORT: 12345,
  366. }
  367. srv = self.get_ssdp_discovery_server(**config)
  368. srv.create_datagram_endpoint = MagicMock()
  369. srv.log = MagicMock()
  370. trnsp = MagicMock()
  371. proto = MagicMock()
  372. loop = MagicMock()
  373. loop.run_until_complete = MagicMock(return_value=(trnsp, proto))
  374. io = MagicMock()
  375. io.ported = False
  376. io.get_event_loop = MagicMock(return_value=loop)
  377. with patch("salt.utils.ssdp.asyncio", io):
  378. srv.run()
  379. cde_args = io.get_event_loop().create_datagram_endpoint.call_args[1]
  380. cfg_ip_addr, cfg_port = cde_args["local_addr"]
  381. assert io.get_event_loop.called
  382. assert io.get_event_loop().run_until_complete.called
  383. assert io.get_event_loop().create_datagram_endpoint.called
  384. assert io.get_event_loop().run_forever.called
  385. assert trnsp.close.called
  386. assert loop.close.called
  387. assert srv.log.info.called
  388. assert (
  389. srv.log.info.call_args[0][0]
  390. == "Stopping service discovery listener."
  391. )
  392. assert "allow_broadcast" in cde_args
  393. assert cde_args["allow_broadcast"]
  394. assert "local_addr" in cde_args
  395. assert (
  396. not cfg_ip_addr == ssdp.SSDPBase.DEFAULTS[ssdp.SSDPBase.LISTEN_IP]
  397. and cfg_ip_addr == "10.10.10.10"
  398. )
  399. assert (
  400. not cfg_port == ssdp.SSDPBase.DEFAULTS[ssdp.SSDPBase.PORT]
  401. and cfg_port == 12345
  402. )
  403. @skipIf(pytest is None, "PyTest is missing")
  404. class SSDPClientTestCase(TestCase, Mocks):
  405. """
  406. Client-related test cases
  407. """
  408. class Resource(object):
  409. """
  410. Fake network reader
  411. """
  412. def __init__(self):
  413. self.pool = [
  414. ("some", "10.10.10.10"),
  415. ("data", "20.20.20.20"),
  416. ("data", "10.10.10.10"),
  417. (None, None),
  418. ]
  419. def read(self, *args, **kwargs):
  420. return self.pool.pop(0)
  421. def test_config_passed(self):
  422. """
  423. Test if the configuration is passed.
  424. :return:
  425. """
  426. config = {
  427. ssdp.SSDPBase.SIGNATURE: "SUSE Enterprise Server",
  428. ssdp.SSDPBase.TIMEOUT: 5,
  429. ssdp.SSDPBase.PORT: 12345,
  430. }
  431. clnt = self.get_ssdp_discovery_client(**config)
  432. assert clnt._config[ssdp.SSDPBase.SIGNATURE] == config[ssdp.SSDPBase.SIGNATURE]
  433. assert clnt._config[ssdp.SSDPBase.PORT] == config[ssdp.SSDPBase.PORT]
  434. assert clnt._config[ssdp.SSDPBase.TIMEOUT] == config[ssdp.SSDPBase.TIMEOUT]
  435. def test_config_detached(self):
  436. """
  437. Test if the passed configuration is not a reference.
  438. :return:
  439. """
  440. config = {
  441. ssdp.SSDPBase.SIGNATURE: "SUSE Enterprise Server",
  442. }
  443. clnt = self.get_ssdp_discovery_client(**config)
  444. clnt._config["foo"] = "bar"
  445. assert "foo" in clnt._config
  446. assert "foo" not in config
  447. def test_query(self):
  448. """
  449. Test if client queries the broadcast
  450. :return:
  451. """
  452. config = {
  453. ssdp.SSDPBase.SIGNATURE: "SUSE Enterprise Server",
  454. ssdp.SSDPBase.PORT: 4000,
  455. }
  456. f_time = 1111
  457. _socket = MagicMock()
  458. with patch("salt.utils.ssdp.socket", _socket), patch(
  459. "salt.utils.ssdp.time.time", MagicMock(return_value=f_time)
  460. ):
  461. clnt = ssdp.SSDPDiscoveryClient(**config)
  462. clnt._query()
  463. assert clnt._socket.sendto.called
  464. message, target = clnt._socket.sendto.call_args[0]
  465. assert message == salt.utils.stringutils.to_bytes(
  466. "{}{}".format(config[ssdp.SSDPBase.SIGNATURE], f_time)
  467. )
  468. assert target[0] == "<broadcast>"
  469. assert target[1] == config[ssdp.SSDPBase.PORT]
  470. def test_get_masters_map(self):
  471. """
  472. Test getting map of the available masters on the network
  473. :return:
  474. """
  475. _socket = MagicMock()
  476. response = {}
  477. with patch("salt.utils.ssdp.socket", _socket):
  478. clnt = ssdp.SSDPDiscoveryClient()
  479. clnt._socket.recvfrom = SSDPClientTestCase.Resource().read
  480. clnt.log = MagicMock()
  481. clnt._collect_masters_map(response=response)
  482. assert "10.10.10.10" in response
  483. assert "20.20.20.20" in response
  484. assert response["10.10.10.10"] == ["some", "data"]
  485. assert response["20.20.20.20"] == ["data"]
  486. def test_get_masters_map_error_handling(self):
  487. """
  488. Test getting map handles timeout network exception
  489. :return:
  490. """
  491. _socket = MagicMock()
  492. response = {}
  493. error_msg = "fake testing timeout just had happened"
  494. with patch("salt.utils.ssdp.socket", _socket):
  495. clnt = ssdp.SSDPDiscoveryClient()
  496. clnt._socket.recvfrom = MagicMock(side_effect=Exception(error_msg))
  497. clnt.log = MagicMock()
  498. clnt._collect_masters_map(response=response)
  499. assert clnt.log.error.called
  500. assert (
  501. "Discovery master collection failure" in clnt.log.error.call_args[0][0]
  502. )
  503. assert error_msg == six.text_type(clnt.log.error.call_args[0][1])
  504. assert not response
  505. def test_discover_no_masters(self):
  506. """
  507. Test discover available master on the network (none found).
  508. :return:
  509. """
  510. clnt = self.get_ssdp_discovery_client()
  511. clnt._query = MagicMock()
  512. clnt._collect_masters_map = MagicMock()
  513. clnt.log = MagicMock()
  514. clnt.discover()
  515. assert clnt.log.info.called
  516. assert clnt.log.info.call_args[0][0] == "No master has been discovered."
  517. def test_discover_general_error(self):
  518. """
  519. Test discover available master on the network (erroneous found)
  520. :return:
  521. """
  522. _socket = MagicMock()
  523. error = "Admins on strike due to broken coffee machine"
  524. signature = ssdp.SSDPBase.DEFAULTS[ssdp.SSDPBase.SIGNATURE]
  525. fake_resource = SSDPClientTestCase.Resource()
  526. fake_resource.pool = [
  527. ("{}:E:{}".format(signature, error), "10.10.10.10"),
  528. (None, None),
  529. ]
  530. with patch("salt.utils.ssdp.socket", _socket):
  531. clnt = ssdp.SSDPDiscoveryClient()
  532. clnt._socket.recvfrom = fake_resource.read
  533. clnt._query = MagicMock()
  534. clnt.log = MagicMock()
  535. clnt.discover()
  536. assert len(clnt.log.error.mock_calls) == 1
  537. assert (
  538. "Error response from the service publisher"
  539. in clnt.log.error.call_args[0][0]
  540. )
  541. assert "10.10.10.10" == clnt.log.error.call_args[0][1]
  542. assert clnt.log.error.call_args[1] == {}
  543. assert clnt.log.error.call_args[0][2] == error
  544. def test_discover_timestamp_error(self):
  545. """
  546. Test discover available master on the network (outdated timestamp)
  547. :return:
  548. """
  549. _socket = MagicMock()
  550. error = "We only support a 1200 bps connection. Routing timestamp problems on neural net."
  551. signature = ssdp.SSDPBase.DEFAULTS[ssdp.SSDPBase.SIGNATURE]
  552. fake_resource = SSDPClientTestCase.Resource()
  553. fake_resource.pool = [
  554. ("{}:E:{}".format(signature, error), "10.10.10.10"),
  555. (None, None),
  556. ]
  557. with patch("salt.utils.ssdp.socket", _socket):
  558. clnt = ssdp.SSDPDiscoveryClient()
  559. clnt._socket.recvfrom = fake_resource.read
  560. clnt._query = MagicMock()
  561. clnt.log = MagicMock()
  562. clnt.discover()
  563. assert len(clnt.log.error.mock_calls) == 2
  564. assert (
  565. "Error response from the service publisher"
  566. in clnt.log.error.mock_calls[0][1][0]
  567. )
  568. assert clnt.log.error.mock_calls[0][1][2] == error
  569. assert clnt.log.error.mock_calls[0][2] == {}
  570. assert (
  571. "Publisher sent shifted timestamp" in clnt.log.error.mock_calls[1][1][0]
  572. )
  573. assert (
  574. clnt.log.error.mock_calls[1][1][1]
  575. == clnt.log.error.mock_calls[0][1][1]
  576. == "10.10.10.10"
  577. )