Exemple #1
0
 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
Exemple #4
0
    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")
Exemple #5
0
 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()
Exemple #6
0
    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")
Exemple #7
0
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
Exemple #8
0
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
Exemple #9
0
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
Exemple #10
0
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)