# Buggy code
def eval_master(self, opts, timeout=60, safe=True, failed=False):
    """
    Evaluates and returns a tuple of the current master address and the pub_channel.

    In standard mode, just creates a pub_channel with the given master address.

    With master_type=func evaluates the current master address from the given
    module and then creates a pub_channel.

    With master_type=failover takes the list of masters and loops through them.
    The first one that allows the minion to create a pub_channel is then
    returned. If this function is called outside the minions initialization
    phase (for example from the minions main event-loop when a master connection
    loss was detected), 'failed' should be set to True. The current
    (possibly failed) master will then be removed from the list of masters.
    """
    # check if master_type was altered from its default
    if opts["master_type"] != "str" and opts["__role"] != "syndic":
        # check for a valid keyword
        if opts["master_type"] == "func":
            # split module and function and try loading the module
            mod, fun = opts["master"].split(".")
            try:
                master_mod = salt.loader.raw_mod(opts, mod, fun)
                if not master_mod:
                    raise TypeError
                # we take whatever the module returns as master address
                opts["master"] = master_mod[mod + "." + fun]()
            except TypeError:
                msg = "Failed to evaluate master address from module '{0}'".format(
                    opts["master"]
                )
                log.error(msg)
                sys.exit(salt.defaults.exitcodes.EX_GENERIC)
            log.info("Evaluated master from module: {0}".format(master_mod))

        # if failover is set, master has to be of type list
        elif opts["master_type"] == "failover":
            if isinstance(opts["master"], list):
                log.info(
                    "Got list of available master addresses: {0}".format(opts["master"])
                )
                if opts["master_shuffle"]:
                    shuffle(opts["master"])
            # if opts['master'] is a str and we have never created opts['master_list']
            elif isinstance(opts["master"], str) and ("master_list" not in opts):
                # We have a string, but a list was what was intended. Convert.
                # See issue 23611 for details
                opts["master"] = [opts["master"]]
            elif opts["__role"] == "syndic":
                log.info("Syndic setting master_syndic to '{0}'".format(opts["master"]))

            # if failed=True, the minion was previously connected
            # we're probably called from the minions main-event-loop
            # because a master connection loss was detected. remove
            # the possibly failed master from the list of masters.
            elif failed:
                log.info(
                    "Removing possibly failed master {0} from list of masters".format(
                        opts["master"]
                    )
                )
                # create new list of master with the possibly failed one removed
                opts["master"] = [x for x in opts["master_list"] if opts["master"] != x]

            else:
                msg = (
                    "master_type set to 'failover' but 'master' "
                    "is not of type list but of type "
                    "{0}".format(type(opts["master"]))
                )
                log.error(msg)
                sys.exit(salt.defaults.exitcodes.EX_GENERIC)
            # If failover is set, minion have to failover on DNS errors instead of retry DNS resolve.
            # See issue 21082 for details
            if opts["retry_dns"]:
                msg = (
                    "'master_type' set to 'failover' but 'retry_dns' is not 0. "
                    "Setting 'retry_dns' to 0 to failover to the next master on DNS errors."
                )
                log.critical(msg)
                opts["retry_dns"] = 0
        else:
            msg = "Invalid keyword '{0}' for variable 'master_type'".format(
                opts["master_type"]
            )
            log.error(msg)
            sys.exit(salt.defaults.exitcodes.EX_GENERIC)

    # FIXME: if SMinion don't define io_loop, it can't switch master see #29088
    # Specify kwargs for the channel factory so that SMinion doesn't need to define an io_loop
    # (The channel factories will set a default if the kwarg isn't passed)
    factory_kwargs = {"timeout": timeout, "safe": safe}
    if getattr(self, "io_loop", None):
        factory_kwargs["io_loop"] = self.io_loop

    # if we have a list of masters, loop through them and be
    # happy with the first one that allows us to connect
    if isinstance(opts["master"], list):
        conn = False
        # shuffle the masters and then loop through them
        local_masters = copy.copy(opts["master"])

        for master in local_masters:
            opts["master"] = master
            opts.update(prep_ip_port(opts))
            opts.update(resolve_dns(opts))
            self.opts = opts

            # on first run, update self.opts with the whole master list
            # to enable a minion to re-use old masters if they get fixed
            if "master_list" not in opts:
                opts["master_list"] = local_masters

            try:
                pub_channel = salt.transport.client.AsyncPubChannel.factory(
                    opts, **factory_kwargs
                )
                yield pub_channel.connect()
                conn = True
                break
            except SaltClientError:
                msg = (
                    "Master {0} could not be reached, trying "
                    "next master (if any)".format(opts["master"])
                )
                log.info(msg)
                continue

        if not conn:
            self.connected = False
            msg = (
                "No master could be reached or all masters denied "
                "the minions connection attempt."
            )
            log.error(msg)
        else:
            self.tok = pub_channel.auth.gen_token("salt")
            self.connected = True
            raise tornado.gen.Return((opts["master"], pub_channel))

    # single master sign in
    else:
        opts.update(prep_ip_port(opts))
        opts.update(resolve_dns(opts))
        pub_channel = salt.transport.client.AsyncPubChannel.factory(
            self.opts, **factory_kwargs
        )
        yield pub_channel.connect()
        self.tok = pub_channel.auth.gen_token("salt")
        self.connected = True
        raise tornado.gen.Return((opts["master"], pub_channel))


# Traceback (AttributeError)
# salt-minion -l debug
[DEBUG   ] Reading configuration from /etc/salt/minion
[DEBUG   ] Including configuration from '/etc/salt/minion.d/_schedule.conf'
[DEBUG   ] Reading configuration from /etc/salt/minion.d/_schedule.conf
[DEBUG   ] Including configuration from '/etc/salt/minion.d/uplynk.conf'
[DEBUG   ] Reading configuration from /etc/salt/minion.d/uplynk.conf
[DEBUG   ] Using cached minion ID from /etc/salt/minion_id: dusw1-content17
[DEBUG   ] Reading configuration from /etc/salt/minion
[DEBUG   ] Including configuration from '/etc/salt/minion.d/_schedule.conf'
[DEBUG   ] Reading configuration from /etc/salt/minion.d/_schedule.conf
[DEBUG   ] Including configuration from '/etc/salt/minion.d/uplynk.conf'
[DEBUG   ] Reading configuration from /etc/salt/minion.d/uplynk.conf
[DEBUG   ] Please install 'virt-what' to improve results of the 'virtual' grain.
[INFO    ] Processing `log_handlers.sentry`
[DEBUG   ] No 'sentry_handler' key was found in the configuration
[INFO    ] The `log_handlers.sentry.setup_handlers()` function returned `False` which means no logging handler was configured on purpose. Continuing...
[DEBUG   ] Configuration file path: /etc/salt/minion
[WARNING ] Insecure logging configuration detected! Sensitive data may be logged.
[INFO    ] Setting up the Salt Minion "dusw1-content17"
[DEBUG   ] Created pidfile: /var/run/salt-minion.pid
[DEBUG   ] Reading configuration from /etc/salt/minion
[DEBUG   ] Including configuration from '/etc/salt/minion.d/_schedule.conf'
[DEBUG   ] Reading configuration from /etc/salt/minion.d/_schedule.conf
[DEBUG   ] Including configuration from '/etc/salt/minion.d/uplynk.conf'
[DEBUG   ] Reading configuration from /etc/salt/minion.d/uplynk.conf
[DEBUG   ] Please install 'virt-what' to improve results of the 'virtual' grain.
[INFO    ] The salt minion is starting up
[INFO    ] Minion is starting as user 'root'
[DEBUG   ] AsyncEventPublisher PUB socket URI: ipc:///var/run/salt/minion/minion_event_a6604d7069_pub.ipc
[DEBUG   ] AsyncEventPublisher PULL socket URI: ipc:///var/run/salt/minion/minion_event_a6604d7069_pull.ipc
[INFO    ] Starting pub socket on ipc:///var/run/salt/minion/minion_event_a6604d7069_pub.ipc
[INFO    ] Starting pull socket on ipc:///var/run/salt/minion/minion_event_a6604d7069_pull.ipc
[DEBUG   ] Minion 'dusw1-content17' trying to tune in
[DEBUG   ] sync_connect_master
[INFO    ] Got list of available master addresses: ['smaster2-dusw.xxxx.com', 'smaster1-dusw.xxxx.com']
[DEBUG   ] Initializing new SAuth for ('/etc/salt/pki/minion', 'dusw1-content17', 'tcp://172.31.32.38:4506')
[DEBUG   ] Generated random reconnect delay between '1000ms' and '11000ms' (6337)
[DEBUG   ] Setting zmq_reconnect_ivl to '6337ms'
[DEBUG   ] Setting zmq_reconnect_ivl_max to '11000ms'
[DEBUG   ] Initializing new AsyncZeroMQReqChannel for ('/etc/salt/pki/minion', 'dusw1-content17', 'tcp://172.31.32.38:4506', 'clear')
[DEBUG   ] Handling event '_minion_mine\n\n\x85\xa5clear\xc2\xa3cmd\xa5_mine\xa4data\x80\xa2id\xafdusw1-content17\xa6_stamp\xba2016-01-06T19:26:05.664440'
[DEBUG   ] Initializing new AsyncZeroMQReqChannel for ('/etc/salt/pki/minion', 'dusw1-content17', 'tcp://172.31.32.38:4506', 'aes')
[DEBUG   ] Initializing new SAuth for ('/etc/salt/pki/minion', 'dusw1-content17', 'tcp://172.31.32.38:4506')
[ERROR   ] Exception in callback <functools.partial object at 0x7f2cb472f788>
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/tornado/ioloop.py", line 592, in _run_callback
ret = callback()
File "/usr/lib/python2.7/dist-packages/tornado/stack_context.py", line 275, in null_wrapper
return fn(*args, **kwargs)
File "/usr/lib/python2.7/dist-packages/tornado/ioloop.py", line 598, in <lambda>
self.add_future(ret, lambda f: f.result())
File "/usr/lib/python2.7/dist-packages/tornado/concurrent.py", line 215, in result
raise_exc_info(self._exc_info)
File "/usr/lib/python2.7/dist-packages/tornado/gen.py", line 230, in wrapper
yielded = next(result)
File "/usr/lib/python2.7/dist-packages/salt/minion.py", line 1528, in handle_event
self._mine_send(package)
File "/usr/lib/python2.7/dist-packages/salt/minion.py", line 1498, in _mine_send
load['tok'] = self.tok
AttributeError: 'Minion' object has no attribute 'tok'
[DEBUG   ] SaltReqTimeoutError, retrying. (1/7)
[DEBUG   ] Handling event '_minion_mine\n\n\x85\xa5clear\xc2\xa3cmd\xa5_mine\xa4data\x80\xa2id\xafdusw1-content17\xa6_stamp\xba2016-01-06T19:27:05.759040'
[DEBUG   ] Initializing new AsyncZeroMQReqChannel for ('/etc/salt/pki/minion', 'dusw1-content17', 'tcp://172.31.32.38:4506', 'aes')
[DEBUG   ] Initializing new SAuth for ('/etc/salt/pki/minion', 'dusw1-content17', 'tcp://172.31.32.38:4506')
[ERROR   ] Exception in callback <functools.partial object at 0x7f2cb46d2b50>
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/tornado/ioloop.py", line 592, in _run_callback
ret = callback()
File "/usr/lib/python2.7/dist-packages/tornado/stack_context.py", line 275, in null_wrapper
return fn(*args, **kwargs)
File "/usr/lib/python2.7/dist-packages/tornado/ioloop.py", line 598, in <lambda>
self.add_future(ret, lambda f: f.result())
File "/usr/lib/python2.7/dist-packages/tornado/concurrent.py", line 215, in result
raise_exc_info(self._exc_info)
File "/usr/lib/python2.7/dist-packages/tornado/gen.py", line 230, in wrapper
yielded = next(result)
File "/usr/lib/python2.7/dist-packages/salt/minion.py", line 1528, in handle_event
self._mine_send(package)
File "/usr/lib/python2.7/dist-packages/salt/minion.py", line 1498, in _mine_send
load['tok'] = self.tok
AttributeError: 'Minion' object has no attribute 'tok'

# Fixed code
def eval_master(self, opts, timeout=60, safe=True, failed=False):
    """
    Evaluates and returns a tuple of the current master address and the pub_channel.

    In standard mode, just creates a pub_channel with the given master address.

    With master_type=func evaluates the current master address from the given
    module and then creates a pub_channel.

    With master_type=failover takes the list of masters and loops through them.
    The first one that allows the minion to create a pub_channel is then
    returned. If this function is called outside the minions initialization
    phase (for example from the minions main event-loop when a master connection
    loss was detected), 'failed' should be set to True. The current
    (possibly failed) master will then be removed from the list of masters.
    """
    # check if master_type was altered from its default
    if opts["master_type"] != "str" and opts["__role"] != "syndic":
        # check for a valid keyword
        if opts["master_type"] == "func":
            # split module and function and try loading the module
            mod, fun = opts["master"].split(".")
            try:
                master_mod = salt.loader.raw_mod(opts, mod, fun)
                if not master_mod:
                    raise TypeError
                # we take whatever the module returns as master address
                opts["master"] = master_mod[mod + "." + fun]()
            except TypeError:
                msg = "Failed to evaluate master address from module '{0}'".format(
                    opts["master"]
                )
                log.error(msg)
                sys.exit(salt.defaults.exitcodes.EX_GENERIC)
            log.info("Evaluated master from module: {0}".format(master_mod))

        # if failover is set, master has to be of type list
        elif opts["master_type"] == "failover":
            if isinstance(opts["master"], list):
                log.info(
                    "Got list of available master addresses: {0}".format(opts["master"])
                )
                if opts["master_shuffle"]:
                    shuffle(opts["master"])
                opts["auth_tries"] = 0
            # if opts['master'] is a str and we have never created opts['master_list']
            elif isinstance(opts["master"], str) and ("master_list" not in opts):
                # We have a string, but a list was what was intended. Convert.
                # See issue 23611 for details
                opts["master"] = [opts["master"]]
            elif opts["__role"] == "syndic":
                log.info("Syndic setting master_syndic to '{0}'".format(opts["master"]))

            # if failed=True, the minion was previously connected
            # we're probably called from the minions main-event-loop
            # because a master connection loss was detected. remove
            # the possibly failed master from the list of masters.
            elif failed:
                log.info(
                    "Removing possibly failed master {0} from list of masters".format(
                        opts["master"]
                    )
                )
                # create new list of master with the possibly failed one removed
                opts["master"] = [x for x in opts["master_list"] if opts["master"] != x]

            else:
                msg = (
                    "master_type set to 'failover' but 'master' "
                    "is not of type list but of type "
                    "{0}".format(type(opts["master"]))
                )
                log.error(msg)
                sys.exit(salt.defaults.exitcodes.EX_GENERIC)
            # If failover is set, minion have to failover on DNS errors instead of retry DNS resolve.
            # See issue 21082 for details
            if opts["retry_dns"]:
                msg = (
                    "'master_type' set to 'failover' but 'retry_dns' is not 0. "
                    "Setting 'retry_dns' to 0 to failover to the next master on DNS errors."
                )
                log.critical(msg)
                opts["retry_dns"] = 0
        else:
            msg = "Invalid keyword '{0}' for variable 'master_type'".format(
                opts["master_type"]
            )
            log.error(msg)
            sys.exit(salt.defaults.exitcodes.EX_GENERIC)

    # FIXME: if SMinion don't define io_loop, it can't switch master see #29088
    # Specify kwargs for the channel factory so that SMinion doesn't need to define an io_loop
    # (The channel factories will set a default if the kwarg isn't passed)
    factory_kwargs = {"timeout": timeout, "safe": safe}
    if getattr(self, "io_loop", None):
        factory_kwargs["io_loop"] = self.io_loop

    # if we have a list of masters, loop through them and be
    # happy with the first one that allows us to connect
    if isinstance(opts["master"], list):
        conn = False
        # shuffle the masters and then loop through them
        local_masters = copy.copy(opts["master"])

        for master in local_masters:
            opts["master"] = master
            opts.update(prep_ip_port(opts))
            opts.update(resolve_dns(opts))
            self.opts = opts

            # on first run, update self.opts with the whole master list
            # to enable a minion to re-use old masters if they get fixed
            if "master_list" not in opts:
                opts["master_list"] = local_masters

            try:
                pub_channel = salt.transport.client.AsyncPubChannel.factory(
                    opts, **factory_kwargs
                )
                yield pub_channel.connect()
                conn = True
                break
            except SaltClientError:
                msg = (
                    "Master {0} could not be reached, trying "
                    "next master (if any)".format(opts["master"])
                )
                log.info(msg)
                continue

        if not conn:
            self.connected = False
            msg = (
                "No master could be reached or all masters denied "
                "the minions connection attempt."
            )
            log.error(msg)
        else:
            self.tok = pub_channel.auth.gen_token("salt")
            self.connected = True
            raise tornado.gen.Return((opts["master"], pub_channel))

    # single master sign in
    else:
        opts.update(prep_ip_port(opts))
        opts.update(resolve_dns(opts))
        pub_channel = salt.transport.client.AsyncPubChannel.factory(
            self.opts, **factory_kwargs
        )
        yield pub_channel.connect()
        self.tok = pub_channel.auth.gen_token("salt")
        self.connected = True
        raise tornado.gen.Return((opts["master"], pub_channel))