def test_dict_to_txt_array(self): self.assertEqual( avahi.dict_to_txt_array( OrderedDict((('a', 'abc'), ('b', b'\x01'), ('c', u'\u00e1')))), [ [dbus.Byte(97), dbus.Byte(ord('=')), dbus.Byte(97), dbus.Byte(98), dbus.Byte(99)], [dbus.Byte(98), dbus.Byte(ord('=')), dbus.Byte(0x01)], [dbus.Byte(99), dbus.Byte(ord('=')), dbus.Byte(0xc3), dbus.Byte(0xa1)]]) self.assertIsInstance( avahi.dict_to_txt_array({'a': 'abc'})[0][0], dbus.Byte)
def __init__(self, name, type, port, txt, hostname=None, proto=avahi.PROTO_INET): self.name = name self.type = type self.port = port self.txt = txt self.proto = proto self.bus = dbus.SystemBus() self.server = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) entry_path = self.server.EntryGroupNew() entry_obj = self.bus.get_object(avahi.DBUS_NAME, entry_path) entry = dbus.Interface(entry_obj, avahi.DBUS_INTERFACE_ENTRY_GROUP) if hostname is None: hostname = get_host_name_fqdn() entry.AddService(avahi.IF_UNSPEC, self.proto, dbus.UInt32(0), name, type, get_domain_name(), hostname, port, avahi.dict_to_txt_array(txt)) entry.Commit() self.entry = entry
def __init__(self, service_name='Demo Service', service_type='_demo._tcp', service_port=8899, service_txt={}, domain='', host=''): self.log = logging.getLogger() #self.loop = loop or DBusGMainLoop() self.bus = dbus.SystemBus() self.server = dbus.Interface( self.bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ), avahi.DBUS_INTERFACE_SERVER ) self.service_name = service_name #See http://www.dns-sd.org/ServiceTypes.html self.service_type = service_type self.service_port = service_port self.service_txt = avahi.dict_to_txt_array(service_txt) #TXT record for the service self.domain = domain # Domain to publish on, default to .local self.host = host # Host to publish records for, default to localhost self.group = None # Counter so we only rename after collisions a sensible number of times self.rename_count = 12
def serve(self): """ Start AMQP service for this clacks service provider. """ # Load AMQP and Command registry instances amqp = PluginRegistry.getInstance('AMQPHandler') self.__cr = PluginRegistry.getInstance('CommandRegistry') # Create a list of queues we need here queues = {} for dsc in self.__cr.commands.values(): queues[dsc['target']] = True # Finally create the queues for queue in queues: # Add round robin processor for queue self.__cmdWorker = AMQPWorker(self.env, connection=amqp.getConnection(), r_address='%s.command.%s; { create:always, node:{ type:queue, x-bindings:[ { exchange:"amq.direct", queue:"%s.command.%s" } ] } }' % (self.env.domain, queue, self.env.domain, queue), workers=int(self.env.config.get('amqp.worker', default=1)), callback=self.commandReceived) # Add private processor for queue self.__cmdWorker = AMQPWorker(self.env, connection=amqp.getConnection(), r_address='%s.command.%s.%s; { create:always, delete:receiver, node:{ type:queue, x-bindings:[ { exchange:"amq.direct", queue:"%s.command.%s.%s" } ] } }' % (self.env.domain, queue, self.env.id, self.env.domain, queue, self.env.id), workers=int(self.env.config.get('amqp.worker', default=1)), callback=self.commandReceived) # Announce service if self.env.config.get("amqp.announce", default="True").lower() == "true": url = parseURL(self.env.config.get("amqp.url")) self.__zeroconf = ZeroconfService(name="Clacks RPC service", port=url['port'], stype="_%s._tcp" % url['scheme'], text=dict_to_txt_array({'path': self.env.domain, 'service': 'clacks'})) self.__zeroconf.publish() self.log.info("ready to process incoming requests")
def publish(self): self.group.addService( avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), "HomeSync Server", "_homesync._tcp", "","", dbus.UInt16(self.port), avahi.dict_to_txt_array({"HomeSync":1,"UID":500}) ) self.group.Commit()
def serve(self): """ Start JSONRPC service for this clacks service provider. """ # Get http service instance self.__http = PluginRegistry.getInstance('HTTPService') cr = PluginRegistry.getInstance('CommandRegistry') # Register ourselves self.__app = JsonRpcApp(cr) self.__http.app.register(self.path, AuthCookieHandler(self.__app, timeout=self.env.config.get('http.cookie-lifetime', default=1800), cookie_name='ClacksRPC', secret=self.env.config.get('http.cookie-secret', default="TecloigJink4"))) # Announce service if self.env.config.get("http.announce", default="True").lower() == "true": self.__zeroconf = ZeroconfService(name="Clacks RPC service", port=self.__http.port, stype="_%s._tcp" % self.__http.scheme, text=dict_to_txt_array({'path': self.path, 'service': 'clacks'})) self.__zeroconf.publish() self.log.info("ready to process incoming requests")
def beacon(config): ''' Broadcast values via zeroconf If the announced values are static, it is adviced to set run_once: True (do not poll) on the beacon configuration. The following are required configuration settings: 'servicetype': The service type to announce. 'port': The port of the service to announce. 'txt': The TXT record of the service being announced as a dict. Grains can be used to define TXT values using the syntax: grains.<grain_name> or: grains.<grain_name>[i] where i is an integer representing the index of the grain to use. If the grain is not a list, the index is ignored. The following are optional configuration settings: 'servicename': Set the name of the service. Will use the hostname from __grains__['host'] if not set. 'reset_on_change': If true and there is a change in TXT records detected, it will stop announcing the service and then restart announcing the service. This interruption in service announcement may be desirable if the client relies on changes in the browse records to update its cache of the TXT records. Defaults to False. 'reset_wait': The number of seconds to wait after announcement stops announcing and before it restarts announcing in the case where there is a change in TXT records detected and 'reset_on_change' is True. Defaults to 0. 'copy_grains': If set to True, it will copy the grains passed into the beacon when it backs them up to check for changes on the next iteration. Normally, instead of copy, it would use straight value assignment. This will allow detection of changes to grains where the grains are modified in-place instead of completely replaced. In-place grains changes are not currently done in the main Salt code but may be done due to a custom plug-in. Defaults to False. Example Config .. code-block:: yaml beacons: avahi_announce: run_once: True servicetype: _demo._tcp port: 1234 txt: ProdName: grains.productname SerialNo: grains.serialnumber Comments: 'this is a test' ''' ret = [] changes = {} txt = {} global LAST_GRAINS _validate = __validate__(config) if not _validate[0]: log.warning('Beacon {0} configuration invalid, ' 'not adding. {1}'.format(__virtualname__, _validate[1])) return ret if 'servicename' in config: servicename = config['servicename'] else: servicename = __grains__['host'] for item in config['txt']: if config['txt'][item].startswith('grains.'): grain = config['txt'][item][7:] grain_index = None square_bracket = grain.find('[') if square_bracket != -1 and grain[-1] == ']': grain_index = int(grain[square_bracket + 1:-1]) grain = grain[:square_bracket] grain_value = __grains__.get(grain, '') if isinstance(grain_value, list): if grain_index is not None: grain_value = grain_value[grain_index] else: grain_value = ','.join(grain_value) txt[item] = grain_value if LAST_GRAINS and (LAST_GRAINS.get(grain, '') != __grains__.get( grain, '')): changes[str('txt.' + item)] = txt[item] else: txt[item] = config['txt'][item] if not LAST_GRAINS: changes[str('txt.' + item)] = txt[item] if changes: if not LAST_GRAINS: changes['servicename'] = servicename changes['servicetype'] = config['servicetype'] changes['port'] = config['port'] GROUP.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), servicename, config['servicetype'], '', '', dbus.UInt16(config['port']), avahi.dict_to_txt_array(txt)) GROUP.Commit() elif config.get('reset_on_change', False): GROUP.Reset() reset_wait = config.get('reset_wait', 0) if reset_wait > 0: time.sleep(reset_wait) GROUP.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), servicename, config['servicetype'], '', '', dbus.UInt16(config['port']), avahi.dict_to_txt_array(txt)) GROUP.Commit() else: GROUP.UpdateServiceTxt(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), servicename, config['servicetype'], '', avahi.dict_to_txt_array(txt)) ret.append({'tag': 'result', 'changes': changes}) if config.get('copy_grains', False): LAST_GRAINS = __grains__.copy() else: LAST_GRAINS = __grains__ return ret
def beacon(config): ''' Broadcast values via zeroconf If the announced values are static, it is adviced to set run_once: True (do not poll) on the beacon configuration. Grains can be used to define txt values using the syntax: grains.<grain_name> The default servicename its the hostname grain value. Example Config .. code-block:: yaml beacons: avahi_announce: run_once: True servicetype: _demo._tcp txt: ProdName: grains.productname SerialNo: grains.serialnumber Comments: 'this is a test' ''' ret = [] changes = {} txt = {} global LAST_GRAINS _validate = validate(config) if not _validate[0]: log.warning('Beacon {0} configuration invalid, ' 'not adding. {1}'.format(__virtualname__, _validate[1])) return ret if 'servicename' in config: servicename = config['servicename'] else: servicename = __grains__['host'] for item in config['txt']: if config['txt'][item].startswith('grains.'): grain = config['txt'][item][7:] txt[item] = __grains__[grain] if LAST_GRAINS and (LAST_GRAINS[grain] != __grains__[grain]): changes[str('txt.' + item)] = txt[item] else: txt[item] = config['txt'][item] if not LAST_GRAINS: changes[str('txt.' + item)] = txt[item] if changes: if not LAST_GRAINS: changes['servicename'] = servicename changes['servicetype'] = config['servicetype'] changes['port'] = config['port'] else: GROUP.Reset() GROUP.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), servicename, config['servicetype'], '', '', dbus.UInt16(config['port']), avahi.dict_to_txt_array(txt)) GROUP.Commit() ret.append({'tag': 'result', 'changes': changes}) LAST_GRAINS = __grains__ return ret
def beacon(config): """ Broadcast values via zeroconf If the announced values are static, it is advised to set run_once: True (do not poll) on the beacon configuration. The following are required configuration settings: - ``servicetype`` - The service type to announce - ``port`` - The port of the service to announce - ``txt`` - The TXT record of the service being announced as a dict. Grains can be used to define TXT values using one of following two formats: - ``grains.<grain_name>`` - ``grains.<grain_name>[i]`` where i is an integer representing the index of the grain to use. If the grain is not a list, the index is ignored. The following are optional configuration settings: - ``servicename`` - Set the name of the service. Will use the hostname from the minion's ``host`` grain if this value is not set. - ``reset_on_change`` - If ``True`` and there is a change in TXT records detected, it will stop announcing the service and then restart announcing the service. This interruption in service announcement may be desirable if the client relies on changes in the browse records to update its cache of TXT records. Defaults to ``False``. - ``reset_wait`` - The number of seconds to wait after announcement stops announcing and before it restarts announcing in the case where there is a change in TXT records detected and ``reset_on_change`` is ``True``. Defaults to ``0``. - ``copy_grains`` - If ``True``, Salt will copy the grains passed into the beacon when it backs them up to check for changes on the next iteration. Normally, instead of copy, it would use straight value assignment. This will allow detection of changes to grains where the grains are modified in-place instead of completely replaced. In-place grains changes are not currently done in the main Salt code but may be done due to a custom plug-in. Defaults to ``False``. Example Config .. code-block:: yaml beacons: avahi_announce: - run_once: True - servicetype: _demo._tcp - port: 1234 - txt: ProdName: grains.productname SerialNo: grains.serialnumber Comments: 'this is a test' """ ret = [] changes = {} txt = {} global LAST_GRAINS _config = {} list(map(_config.update, config)) if "servicename" in _config: servicename = _config["servicename"] else: servicename = __grains__["host"] # Check for hostname change if LAST_GRAINS and LAST_GRAINS["host"] != servicename: changes["servicename"] = servicename if LAST_GRAINS and _config.get("reset_on_change", False): # Check for IP address change in the case when we reset on change if LAST_GRAINS.get("ipv4", []) != __grains__.get("ipv4", []): changes["ipv4"] = __grains__.get("ipv4", []) if LAST_GRAINS.get("ipv6", []) != __grains__.get("ipv6", []): changes["ipv6"] = __grains__.get("ipv6", []) for item in _config["txt"]: changes_key = "txt." + salt.utils.stringutils.to_unicode(item) if _config["txt"][item].startswith("grains."): grain = _config["txt"][item][7:] grain_index = None square_bracket = grain.find("[") if square_bracket != -1 and grain[-1] == "]": grain_index = int(grain[square_bracket + 1 : -1]) grain = grain[:square_bracket] grain_value = __grains__.get(grain, "") if isinstance(grain_value, list): if grain_index is not None: grain_value = grain_value[grain_index] else: grain_value = ",".join(grain_value) txt[item] = _enforce_txt_record_maxlen(item, grain_value) if LAST_GRAINS and ( LAST_GRAINS.get(grain, "") != __grains__.get(grain, "") ): changes[changes_key] = txt[item] else: txt[item] = _enforce_txt_record_maxlen(item, _config["txt"][item]) if not LAST_GRAINS: changes[changes_key] = txt[item] if changes: if not LAST_GRAINS: changes["servicename"] = servicename changes["servicetype"] = _config["servicetype"] changes["port"] = _config["port"] changes["ipv4"] = __grains__.get("ipv4", []) changes["ipv6"] = __grains__.get("ipv6", []) GROUP.AddService( avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), servicename, _config["servicetype"], "", "", dbus.UInt16(_config["port"]), avahi.dict_to_txt_array(txt), ) GROUP.Commit() elif _config.get("reset_on_change", False) or "servicename" in changes: # A change in 'servicename' requires a reset because we can only # directly update TXT records GROUP.Reset() reset_wait = _config.get("reset_wait", 0) if reset_wait > 0: time.sleep(reset_wait) GROUP.AddService( avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), servicename, _config["servicetype"], "", "", dbus.UInt16(_config["port"]), avahi.dict_to_txt_array(txt), ) GROUP.Commit() else: GROUP.UpdateServiceTxt( avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), servicename, _config["servicetype"], "", avahi.dict_to_txt_array(txt), ) ret.append({"tag": "result", "changes": changes}) if _config.get("copy_grains", False): LAST_GRAINS = __grains__.copy() else: LAST_GRAINS = __grains__ return ret
def beacon(config): ''' Broadcast values via zeroconf If the announced values are static, it is adviced to set run_once: True (do not poll) on the beacon configuration. The following are required configuration settings: 'servicetype': The service type to announce. 'port': The port of the service to announce. 'txt': The TXT record of the service being announced as a dict. Grains can be used to define TXT values using the syntax: grains.<grain_name> or: grains.<grain_name>[i] where i is an integer representing the index of the grain to use. If the grain is not a list, the index is ignored. The following are optional configuration settings: 'servicename': Set the name of the service. Will use the hostname from __grains__['host'] if not set. 'reset_on_change': If true and there is a change in TXT records detected, it will stop announcing the service and then restart announcing the service. This interruption in service announcement may be desirable if the client relies on changes in the browse records to update its cache of the TXT records. Defaults to False. 'reset_wait': The number of seconds to wait after announcement stops announcing and before it restarts announcing in the case where there is a change in TXT records detected and 'reset_on_change' is True. Defaults to 0. 'copy_grains': If set to True, it will copy the grains passed into the beacon when it backs them up to check for changes on the next iteration. Normally, instead of copy, it would use straight value assignment. This will allow detection of changes to grains where the grains are modified in-place instead of completely replaced. In-place grains changes are not currently done in the main Salt code but may be done due to a custom plug-in. Defaults to False. Example Config .. code-block:: yaml beacons: avahi_announce: run_once: True servicetype: _demo._tcp port: 1234 txt: ProdName: grains.productname SerialNo: grains.serialnumber Comments: 'this is a test' ''' ret = [] changes = {} txt = {} global LAST_GRAINS _validate = __validate__(config) if not _validate[0]: log.warning('Beacon {0} configuration invalid, ' 'not adding. {1}'.format(__virtualname__, _validate[1])) return ret if 'servicename' in config: servicename = config['servicename'] else: servicename = __grains__['host'] for item in config['txt']: if config['txt'][item].startswith('grains.'): grain = config['txt'][item][7:] grain_index = None square_bracket = grain.find('[') if square_bracket != -1 and grain[-1] == ']': grain_index = int(grain[square_bracket+1:-1]) grain = grain[:square_bracket] grain_value = __grains__.get(grain, '') if isinstance(grain_value, list): if grain_index is not None: grain_value = grain_value[grain_index] else: grain_value = ','.join(grain_value) txt[item] = grain_value if LAST_GRAINS and (LAST_GRAINS.get(grain, '') != __grains__.get(grain, '')): changes[str('txt.' + item)] = txt[item] else: txt[item] = config['txt'][item] if not LAST_GRAINS: changes[str('txt.' + item)] = txt[item] if changes: if not LAST_GRAINS: changes['servicename'] = servicename changes['servicetype'] = config['servicetype'] changes['port'] = config['port'] GROUP.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), servicename, config['servicetype'], '', '', dbus.UInt16(config['port']), avahi.dict_to_txt_array(txt)) GROUP.Commit() elif config.get('reset_on_change', False): GROUP.Reset() reset_wait = config.get('reset_wait', 0) if reset_wait > 0: time.sleep(reset_wait) GROUP.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), servicename, config['servicetype'], '', '', dbus.UInt16(config['port']), avahi.dict_to_txt_array(txt)) GROUP.Commit() else: GROUP.UpdateServiceTxt(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), servicename, config['servicetype'], '', avahi.dict_to_txt_array(txt)) ret.append({'tag': 'result', 'changes': changes}) if config.get('copy_grains', False): LAST_GRAINS = __grains__.copy() else: LAST_GRAINS = __grains__ return ret
def set(self, txt): self.txt = txt self.entry.UpdateServiceTxt(avahi.IF_UNSPEC, self.proto, dbus.UInt32(0), self.name, self.type, get_domain_name(), avahi.dict_to_txt_array(self.txt))
def assert_callback_on_services_calls_back(self, domain=None): """This method runs callback_on_service and checks that it makes the expected dbus calls.""" from nmoscommon.mdns.avahidbus import MDNSEngine UUT = MDNSEngine() callback = mock.MagicMock(name="callback") regtype = "test_type" txtrecord = {'name': "Test Text Record", 'foo': 'bar'} if domain is None: expected_domain = "local" detected_domain = "dummydomain" else: expected_domain = domain self.DBUSInterface(self.DBUSObject('/'), 'org.freedesktop.Avahi.Server' ).DomainBrowserNew.side_effect = [ "/new_domain_browser", Exception ] self.DBUSInterface( self.DBUSObject('/'), 'org.freedesktop.Avahi.Server' ).ServiceBrowserNew.side_effect = lambda i, p, t, domain, n: "/new_service_browser(" + domain + ")" dbrowser = self.DBUSInterface( self.DBUSObject("/new_domain_browser"), avahi.DBUS_INTERFACE_DOMAIN_BROWSER) sbrowser = self.DBUSInterface( self.DBUSObject("/new_service_browser(" + expected_domain + ")"), avahi.DBUS_INTERFACE_SERVICE_BROWSER) if domain is None: dsbrowser = self.DBUSInterface( self.DBUSObject("/new_service_browser(" + detected_domain + ")"), avahi.DBUS_INTERFACE_SERVICE_BROWSER) UUT.callback_on_services(regtype, callback, registerOnly=True, domain=domain) if domain is None: self.DBUSInterface(self.DBUSObject('/'), 'org.freedesktop.Avahi.Server' ).DomainBrowserNew.assert_called_with( avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "", 0, dbus.UInt32(0)) dbrowser.connect_to_signal.assert_called_with( "ItemNew", mock.ANY) self.DBUSInterface(self.DBUSObject('/'), 'org.freedesktop.Avahi.Server' ).ServiceBrowserNew.assert_called_with( avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, regtype, expected_domain, dbus.UInt32(0)) service_callbacks = dict( (call[1][0], call[1][1]) for call in sbrowser.connect_to_signal.mock_calls) self.assertIn("ItemNew", service_callbacks) self.assertIn("ItemRemove", service_callbacks) # Now test the ItemNew Callback calls ResolveService service_callbacks["ItemNew"](mock.sentinel.interface, mock.sentinel.protocol, mock.sentinel.name, mock.sentinel.stype, mock.sentinel.domain, mock.sentinel.flags) self.DBUSInterface( self.DBUSObject('/'), 'org.freedesktop.Avahi.Server' ).ResolveService.assert_called_with(mock.sentinel.interface, mock.sentinel.protocol, mock.sentinel.name, mock.sentinel.stype, mock.sentinel.domain, avahi.PROTO_UNSPEC, dbus.UInt32(0), reply_handler=mock.ANY, error_handler=mock.ANY) # Now check that the reply_handler passed to ResolveService will call back correctly callback.reset_mock() resolve_service_reply_handler = self.DBUSInterface( self.DBUSObject('/'), 'org.freedesktop.Avahi.Server' ).ResolveService.call_args[1]['reply_handler'] resolve_service_reply_handler( mock.sentinel.interface, mock.sentinel.protocol, mock.sentinel.name, mock.sentinel.stype, mock.sentinel.domain, mock.sentinel.host, mock.sentinel.arprotocol, mock.sentinel.address, mock.sentinel.port, avahi.dict_to_txt_array(txtrecord), mock.sentinel.flags) callback.assert_called_once_with({ "action": "add", "name": mock.sentinel.name, "type": mock.sentinel.stype, "address": mock.sentinel.address, "port": mock.sentinel.port, "txt": txtrecord, "interface": mock.sentinel.interface }) # Test error_handler is callable resolve_service_error_handler = self.DBUSInterface( self.DBUSObject('/'), 'org.freedesktop.Avahi.Server' ).ResolveService.call_args[1]['error_handler'] try: resolve_service_error_handler( "Not Really An Error, just testing Error Reporting") except: self.fail( msg= "error_handler passed to ResolveService threw unknown exception: %s" % (traceback.format_exc(), )) # Now test the ItemRemove Callback calls back correctly callback.reset_mock() service_callbacks["ItemRemove"](mock.sentinel.interface, mock.sentinel.protocol, mock.sentinel.name, mock.sentinel._type, mock.sentinel.domain, mock.sentinel.flags) callback.assert_called_once_with({ "action": "remove", "name": mock.sentinel.name, "type": mock.sentinel._type }) if domain is None: # Check that the domain browser callback sets up a new service browser for the new domain self.DBUSInterface(self.DBUSObject('/'), 'org.freedesktop.Avahi.Server' ).ServiceBrowserNew.reset_mock() callback.reset_mock() domain_callbacks = dict( (call[1][0], call[1][1]) for call in dbrowser.connect_to_signal.mock_calls) domain_callbacks['ItemNew'](mock.sentinel.interface, mock.sentinel.protocol, detected_domain, mock.sentinel.flags) self.DBUSInterface(self.DBUSObject('/'), 'org.freedesktop.Avahi.Server' ).ServiceBrowserNew.assert_called_with( avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, regtype, detected_domain, dbus.UInt32(0)) service_callbacks = dict( (call[1][0], call[1][1]) for call in dsbrowser.connect_to_signal.mock_calls) self.assertIn("ItemNew", service_callbacks) self.assertIn("ItemRemove", service_callbacks) # Now test the ItemNew Callback calls ResolveService service_callbacks["ItemNew"](mock.sentinel.interface, mock.sentinel.protocol, mock.sentinel.name, mock.sentinel.stype, mock.sentinel.domain, mock.sentinel.flags) self.DBUSInterface( self.DBUSObject('/'), 'org.freedesktop.Avahi.Server' ).ResolveService.assert_called_with(mock.sentinel.interface, mock.sentinel.protocol, mock.sentinel.name, mock.sentinel.stype, mock.sentinel.domain, avahi.PROTO_UNSPEC, dbus.UInt32(0), reply_handler=mock.ANY, error_handler=mock.ANY) # Now check that the reply_handler passed to ResolveService will call back correctly callback.reset_mock() resolve_service_reply_handler = self.DBUSInterface( self.DBUSObject('/'), 'org.freedesktop.Avahi.Server' ).ResolveService.call_args[1]['reply_handler'] resolve_service_reply_handler( mock.sentinel.interface, mock.sentinel.protocol, mock.sentinel.name, mock.sentinel.stype, mock.sentinel.domain, mock.sentinel.host, mock.sentinel.arprotocol, mock.sentinel.address, mock.sentinel.port, avahi.dict_to_txt_array(txtrecord), mock.sentinel.flags) callback.assert_called_once_with({ "action": "add", "name": mock.sentinel.name, "type": mock.sentinel.stype, "address": mock.sentinel.address, "port": mock.sentinel.port, "txt": txtrecord, "interface": mock.sentinel.interface })
def assert_register_is_correct( self, name, regtype, port, txtRecord, current_state=avahi.ENTRY_GROUP_UNCOMMITED): """This method performs a register with a given name, type, port, etc ... it also allows the current state of the group to be defined so that various of the safety checks can be triggered.""" from nmoscommon.mdns.avahidbus import MDNSEngine UUT = MDNSEngine() callback = mock.MagicMock(name='callback') #EntryGroupNew should return the path of a new Entry Group object self.DBUSInterface( self.DBUSObject('/'), 'org.freedesktop.Avahi.Server').EntryGroupNew.side_effect = [ "/new_entry_group_object", Exception ] entrygroup = self.DBUSInterface( self.DBUSObject("/new_entry_group_object"), avahi.DBUS_INTERFACE_ENTRY_GROUP) entrygroup.GetState.return_value = current_state UUT.register(name, regtype, port, txtRecord=txtRecord, callback=callback) if txtRecord is None: txtRecord = {} if current_state != avahi.ENTRY_GROUP_ESTABLISHED: entrygroup.Reset.assert_not_called() else: entrygroup.Reset.assert_called_once_with() entrygroup.connect_to_signal.assert_called_once_with( "StateChanged", mock.ANY) entrygroup.AddService.assert_called_once_with( avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0), name, regtype, "local", '', port, avahi.dict_to_txt_array(txtRecord)) entrygroup.Commit.assert_called_once_with() # Should have subscribed to the StateChanged signal, make sure it calls through to the right callback entrygroup.connect_to_signal.call_args[0][1]( avahi.ENTRY_GROUP_COLLISION, "SHOULD BE IGNORED") callback.assert_called_once_with({ "action": "collision", "name": name, "regtype": regtype, "port": port, "txtRecord": txtRecord }) callback.reset_mock() entrygroup.connect_to_signal.call_args[0][1]( avahi.ENTRY_GROUP_ESTABLISHED, "SHOULD BE IGNORED") callback.assert_called_once_with({ "action": "established", "name": name, "regtype": regtype, "port": port, "txtRecord": txtRecord }) callback.reset_mock() entrygroup.connect_to_signal.call_args[0][1]( avahi.ENTRY_GROUP_FAILURE, "SHOULD BE IGNORED") callback.assert_called_once_with({ "action": "failure", "name": name, "regtype": regtype, "port": port, "txtRecord": txtRecord }) callback.reset_mock() return (UUT, entrygroup)