# Buggy code
def run(**kwargs):
    """
    Run a single module function or a range of module functions in a batch.
    Supersedes ``module.run`` function, which requires ``m_`` prefix to
    function-specific parameters.

    :param returner:
        Specify a common returner for the whole batch to send the return data

    :param kwargs:
        Pass any arguments needed to execute the function(s)

    .. code-block:: yaml

      some_id_of_state:
        module.run:
          - network.ip_addrs:
            - interface: eth0
          - cloud.create:
            - names:
              - test-isbm-1
              - test-isbm-2
            - ssh_username: sles
            - image: sles12sp2
            - securitygroup: default
            - size: 'c3.large'
            - location: ap-northeast-1
            - delvol_on_destroy: True


    :return:
    """

    if "name" in kwargs:
        kwargs.pop("name")
    ret = {
        "name": kwargs.keys(),
        "changes": {},
        "comment": "",
        "result": None,
    }

    functions = [func for func in kwargs.keys() if "." in func]
    missing = []
    tests = []
    for func in functions:
        func = func.split(":")[0]
        if func not in __salt__:
            missing.append(func)
        elif __opts__["test"]:
            tests.append(func)

    if tests or missing:
        ret["comment"] = " ".join(
            [
                missing
                and "Unavailable function{plr}: {func}.".format(
                    plr=(len(missing) > 1 or ""), func=(", ".join(missing) or "")
                )
                or "",
                tests
                and "Function{plr} {func} to be executed.".format(
                    plr=(len(tests) > 1 or ""), func=(", ".join(tests)) or ""
                )
                or "",
            ]
        ).strip()
        ret["result"] = not (missing or not tests)

    if ret["result"] is None:
        ret["result"] = True

        failures = []
        success = []
        for func in functions:
            _func = func.split(":")[0]
            try:
                func_ret = _call_function(
                    _func, returner=kwargs.get("returner"), func_args=kwargs.get(func)
                )
                if not _get_result(func_ret, ret["changes"].get("ret", {})):
                    if isinstance(func_ret, dict):
                        failures.append(
                            "'{0}' failed: {1}".format(
                                func, func_ret.get("comment", "(error message N/A)")
                            )
                        )
                else:
                    success.append(
                        "{0}: {1}".format(
                            func,
                            func_ret.get("comment", "Success")
                            if isinstance(func_ret, dict)
                            else func_ret,
                        )
                    )
                    ret["changes"][func] = func_ret
            except (SaltInvocationError, TypeError) as ex:
                failures.append("'{0}' failed: {1}".format(func, ex))
        ret["comment"] = ", ".join(failures + success)
        ret["result"] = not bool(failures)

    return ret


# Traceback (TypeError)
[ERROR   ] An un-handled exception from the multiprocessing process 'SignalHandlingMultiprocessingProcess-1:10' was caught:
Traceback (most recent call last):
File "/root/salt/salt/utils/process.py", line 663, in _run
return self._original_run()
File "/usr/lib/python3.5/multiprocessing/process.py", line 93, in run
self._target(*self._args, **self._kwargs)
File "/root/salt/salt/minion.py", line 1402, in _target
Minion._thread_return(minion_instance, opts, data)
File "/root/salt/salt/minion.py", line 1558, in _thread_return
timeout=minion_instance._return_retry_timer()
File "/root/salt/salt/minion.py", line 1734, in _return_pub
ret_val = self._send_req_sync(load, timeout=timeout)
File "/root/salt/salt/minion.py", line 1239, in _send_req_sync
return channel.send(load, timeout=timeout)
File "/root/salt/salt/utils/async.py", line 75, in wrap
ret = self._block_future(ret)
File "/root/salt/salt/utils/async.py", line 85, in _block_future
return future.result()
File "/usr/local/lib/python3.5/dist-packages/tornado/concurrent.py", line 238, in result
raise_exc_info(self._exc_info)
File "<string>", line 4, in raise_exc_info
File "/usr/local/lib/python3.5/dist-packages/tornado/gen.py", line 1063, in run
yielded = self.gen.throw(*exc_info)
File "/root/salt/salt/transport/zeromq.py", line 271, in send
ret = yield self._crypted_transfer(load, tries=tries, timeout=timeout, raw=raw)
File "/usr/local/lib/python3.5/dist-packages/tornado/gen.py", line 1055, in run
value = future.result()
File "/usr/local/lib/python3.5/dist-packages/tornado/concurrent.py", line 238, in result
raise_exc_info(self._exc_info)
File "<string>", line 4, in raise_exc_info
File "/usr/local/lib/python3.5/dist-packages/tornado/gen.py", line 1063, in run
yielded = self.gen.throw(*exc_info)
File "/root/salt/salt/transport/zeromq.py", line 239, in _crypted_transfer
ret = yield _do_transfer()
File "/usr/local/lib/python3.5/dist-packages/tornado/gen.py", line 1055, in run
value = future.result()
File "/usr/local/lib/python3.5/dist-packages/tornado/concurrent.py", line 238, in result
raise_exc_info(self._exc_info)
File "<string>", line 4, in raise_exc_info
File "/usr/local/lib/python3.5/dist-packages/tornado/gen.py", line 307, in wrapper
yielded = next(result)
File "/root/salt/salt/transport/zeromq.py", line 221, in _do_transfer
self._package_load(self.auth.crypticle.dumps(load)),
File "/root/salt/salt/crypt.py", line 1304, in dumps
return self.encrypt(self.PICKLE_PAD + self.serial.dumps(obj))
File "/root/salt/salt/payload.py", line 181, in dumps
return msgpack.dumps(msg, use_bin_type=use_bin_type)
File "/usr/local/lib/python3.5/dist-packages/msgpack/__init__.py", line 47, in packb
return Packer(**kwargs).pack(o)
File "msgpack/_packer.pyx", line 231, in msgpack._packer.Packer.pack (msgpack/_packer.cpp:3661)
File "msgpack/_packer.pyx", line 233, in msgpack._packer.Packer.pack (msgpack/_packer.cpp:3503)
File "msgpack/_packer.pyx", line 192, in msgpack._packer.Packer._pack (msgpack/_packer.cpp:2657)
File "msgpack/_packer.pyx", line 192, in msgpack._packer.Packer._pack (msgpack/_packer.cpp:2657)
File "msgpack/_packer.pyx", line 192, in msgpack._packer.Packer._pack (msgpack/_packer.cpp:2657)
File "msgpack/_packer.pyx", line 228, in msgpack._packer.Packer._pack (msgpack/_packer.cpp:3382)
TypeError: can't serialize dict_keys(['test.echo'])
Process SignalHandlingMultiprocessingProcess-1:10:
Traceback (most recent call last):
File "/usr/lib/python3.5/multiprocessing/process.py", line 249, in _bootstrap
self.run()
File "/root/salt/salt/utils/process.py", line 663, in _run
return self._original_run()
File "/usr/lib/python3.5/multiprocessing/process.py", line 93, in run
self._target(*self._args, **self._kwargs)
File "/root/salt/salt/minion.py", line 1402, in _target
Minion._thread_return(minion_instance, opts, data)
File "/root/salt/salt/minion.py", line 1558, in _thread_return
timeout=minion_instance._return_retry_timer()
File "/root/salt/salt/minion.py", line 1734, in _return_pub
ret_val = self._send_req_sync(load, timeout=timeout)
File "/root/salt/salt/minion.py", line 1239, in _send_req_sync
return channel.send(load, timeout=timeout)
File "/root/salt/salt/utils/async.py", line 75, in wrap
ret = self._block_future(ret)
File "/root/salt/salt/utils/async.py", line 85, in _block_future
return future.result()
File "/usr/local/lib/python3.5/dist-packages/tornado/concurrent.py", line 238, in result
raise_exc_info(self._exc_info)
File "<string>", line 4, in raise_exc_info
File "/usr/local/lib/python3.5/dist-packages/tornado/gen.py", line 1063, in run
yielded = self.gen.throw(*exc_info)
File "/root/salt/salt/transport/zeromq.py", line 271, in send
ret = yield self._crypted_transfer(load, tries=tries, timeout=timeout, raw=raw)
File "/usr/local/lib/python3.5/dist-packages/tornado/gen.py", line 1055, in run
value = future.result()
File "/usr/local/lib/python3.5/dist-packages/tornado/concurrent.py", line 238, in result
raise_exc_info(self._exc_info)
File "<string>", line 4, in raise_exc_info
File "/usr/local/lib/python3.5/dist-packages/tornado/gen.py", line 1063, in run
yielded = self.gen.throw(*exc_info)
File "/root/salt/salt/transport/zeromq.py", line 239, in _crypted_transfer
ret = yield _do_transfer()
File "/usr/local/lib/python3.5/dist-packages/tornado/gen.py", line 1055, in run
value = future.result()
File "/usr/local/lib/python3.5/dist-packages/tornado/concurrent.py", line 238, in result
raise_exc_info(self._exc_info)
File "<string>", line 4, in raise_exc_info
File "/usr/local/lib/python3.5/dist-packages/tornado/gen.py", line 307, in wrapper
yielded = next(result)
File "/root/salt/salt/transport/zeromq.py", line 221, in _do_transfer
self._package_load(self.auth.crypticle.dumps(load)),
File "/root/salt/salt/crypt.py", line 1304, in dumps
return self.encrypt(self.PICKLE_PAD + self.serial.dumps(obj))
File "/root/salt/salt/payload.py", line 181, in dumps
return msgpack.dumps(msg, use_bin_type=use_bin_type)
File "/usr/local/lib/python3.5/dist-packages/msgpack/__init__.py", line 47, in packb
return Packer(**kwargs).pack(o)
File "msgpack/_packer.pyx", line 231, in msgpack._packer.Packer.pack (msgpack/_packer.cpp:3661)
File "msgpack/_packer.pyx", line 233, in msgpack._packer.Packer.pack (msgpack/_packer.cpp:3503)
File "msgpack/_packer.pyx", line 192, in msgpack._packer.Packer._pack (msgpack/_packer.cpp:2657)
File "msgpack/_packer.pyx", line 192, in msgpack._packer.Packer._pack (msgpack/_packer.cpp:2657)
File "msgpack/_packer.pyx", line 192, in msgpack._packer.Packer._pack (msgpack/_packer.cpp:2657)
File "msgpack/_packer.pyx", line 228, in msgpack._packer.Packer._pack (msgpack/_packer.cpp:3382)
TypeError: can't serialize dict_keys(['test.echo'])

# Fixed code
def run(**kwargs):
    """
    Run a single module function or a range of module functions in a batch.
    Supersedes ``module.run`` function, which requires ``m_`` prefix to
    function-specific parameters.

    :param returner:
        Specify a common returner for the whole batch to send the return data

    :param kwargs:
        Pass any arguments needed to execute the function(s)

    .. code-block:: yaml

      some_id_of_state:
        module.run:
          - network.ip_addrs:
            - interface: eth0
          - cloud.create:
            - names:
              - test-isbm-1
              - test-isbm-2
            - ssh_username: sles
            - image: sles12sp2
            - securitygroup: default
            - size: 'c3.large'
            - location: ap-northeast-1
            - delvol_on_destroy: True


    :return:
    """

    if "name" in kwargs:
        kwargs.pop("name")
    ret = {
        "name": list(kwargs),
        "changes": {},
        "comment": "",
        "result": None,
    }

    functions = [func for func in kwargs.keys() if "." in func]
    missing = []
    tests = []
    for func in functions:
        func = func.split(":")[0]
        if func not in __salt__:
            missing.append(func)
        elif __opts__["test"]:
            tests.append(func)

    if tests or missing:
        ret["comment"] = " ".join(
            [
                missing
                and "Unavailable function{plr}: {func}.".format(
                    plr=(len(missing) > 1 or ""), func=(", ".join(missing) or "")
                )
                or "",
                tests
                and "Function{plr} {func} to be executed.".format(
                    plr=(len(tests) > 1 or ""), func=(", ".join(tests)) or ""
                )
                or "",
            ]
        ).strip()
        ret["result"] = not (missing or not tests)

    if ret["result"] is None:
        ret["result"] = True

        failures = []
        success = []
        for func in functions:
            _func = func.split(":")[0]
            try:
                func_ret = _call_function(
                    _func, returner=kwargs.get("returner"), func_args=kwargs.get(func)
                )
                if not _get_result(func_ret, ret["changes"].get("ret", {})):
                    if isinstance(func_ret, dict):
                        failures.append(
                            "'{0}' failed: {1}".format(
                                func, func_ret.get("comment", "(error message N/A)")
                            )
                        )
                else:
                    success.append(
                        "{0}: {1}".format(
                            func,
                            func_ret.get("comment", "Success")
                            if isinstance(func_ret, dict)
                            else func_ret,
                        )
                    )
                    ret["changes"][func] = func_ret
            except (SaltInvocationError, TypeError) as ex:
                failures.append("'{0}' failed: {1}".format(func, ex))
        ret["comment"] = ", ".join(failures + success)
        ret["result"] = not bool(failures)

    return ret