{"action":"created","issue":{"url":"https://api.github.com/repos/saltstack/salt/issues/23481","labels_url":"https://api.github.com/repos/saltstack/salt/issues/23481/labels{/name}","comments_url":"https://api.github.com/repos/saltstack/salt/issues/23481/comments","events_url":"https://api.github.com/repos/saltstack/salt/issues/23481/events","html_url":"https://github.com/saltstack/salt/pull/23481","id":74168731,"number":23481,"title":"Python 3 compatability refactor","user":{"login":"ckochenower","id":3536804,"avatar_url":"https://avatars.githubusercontent.com/u/3536804?v=3","gravatar_id":"","url":"https://api.github.com/users/ckochenower","html_url":"https://github.com/ckochenower","followers_url":"https://api.github.com/users/ckochenower/followers","following_url":"https://api.github.com/users/ckochenower/following{/other_user}","gists_url":"https://api.github.com/users/ckochenower/gists{/gist_id}","starred_url":"https://api.github.com/users/ckochenower/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ckochenower/subscriptions","organizations_url":"https://api.github.com/users/ckochenower/orgs","repos_url":"https://api.github.com/users/ckochenower/repos","events_url":"https://api.github.com/users/ckochenower/events{/privacy}","received_events_url":"https://api.github.com/users/ckochenower/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":6,"created_at":"2015-05-07T23:58:00Z","updated_at":"2015-05-08T17:07:21Z","closed_at":"2015-05-08T02:55:25Z","pull_request":{"url":"https://api.github.com/repos/saltstack/salt/pulls/23481","html_url":"https://github.com/saltstack/salt/pull/23481","diff_url":"https://github.com/saltstack/salt/pull/23481.diff","patch_url":"https://github.com/saltstack/salt/pull/23481.patch"},"body":"sorting using a 'cmp' is no longer supported in Py3.\r\n'key' works in both Py2 and Py3. 'cmp' has been removed in Py3.\r\nString and binary data are treated differently in Py3.\r\n\r\nThe following script proves that the new `key` implementation is a functional equivalent to the `cmp` implementation.\r\n\r\nTo run the \"SCRIPT\" at the end of this comment in Python 2 and 3, do the following:\r\n1. Copy the script to \\<filename\\>.\r\n2. Run the script with Python 2 ...\r\n    <pre>\r\n    python &lt;filename&gt;\r\n    </pre>\r\n3. Note that both cmp and key implementations produce identical results.\r\n4. Replace ...\r\n    <pre>\r\n        if how_to_sort == 'cmp':\r\n            return sorted(hostname_list, cmp=_cmp_hostname)\r\n        else:\r\n            return sorted(hostname_list, key=_key_hostname)\r\n    </pre>\r\n    ... with ...\r\n    <pre>\r\n        return sorted(hostname_list, key=_key_hostname)\r\n    </pre>\r\n    ... and re-run the script using python3...\r\n    <pre>\r\n    python3 \\<filename\\>\r\n    </pre>\r\n\r\nSCRIPT:\r\n<pre>\r\ndef _sort_hostnames(hostname_list, how_to_sort):\r\n    '''\r\n    sort minion ids favoring in order of:\r\n        - FQDN\r\n        - public ipaddress\r\n        - localhost alias\r\n        - private ipaddress\r\n    '''\r\n    # punish matches in order of preference\r\n    punish = [\r\n        'localhost.localdomain',\r\n        'localhost.my.domain',\r\n        'localhost4.localdomain4',\r\n        'localhost',\r\n        'ip6-localhost',\r\n        'ip6-loopback',\r\n        'ipv6-localhost',\r\n        'ipv6-loopback',\r\n        '127.0.2.1',\r\n        '127.0.1.1',\r\n        '127.0.0.1',\r\n        '0.0.0.0',\r\n        '::1',\r\n        'fe00::',\r\n        'fe02::',\r\n    ]\r\n\r\n    def _cmp_hostname(a, b):\r\n        # should never have a space in hostname\r\n        if ' ' in a:\r\n            return 1\r\n        if ' ' in b:\r\n            return -1\r\n\r\n        # punish localhost list\r\n        if a in punish:\r\n            if b in punish:\r\n                return punish.index(a) - punish.index(b)\r\n            return 1\r\n        if b in punish:\r\n            return -1\r\n\r\n        # punish ipv6\r\n        if ':' in a or ':' in b:\r\n            return a.count(':') - b.count(':')\r\n\r\n        # punish ipv4\r\n        a_is_ipv4 = a.count('.') == 3 and not any(c.isalpha() for c in a)\r\n        b_is_ipv4 = b.count('.') == 3 and not any(c.isalpha() for c in b)\r\n        if a_is_ipv4 and a.startswith('127.'):\r\n            return 1\r\n        if b_is_ipv4 and b.startswith('127.'):\r\n            return -1\r\n        if a_is_ipv4 and not b_is_ipv4:\r\n            return 1\r\n        if a_is_ipv4 and b_is_ipv4:\r\n            return 0\r\n        if not a_is_ipv4 and b_is_ipv4:\r\n            return -1\r\n\r\n        # favor hosts with more dots\r\n        diff = b.count('.') - a.count('.')\r\n        if diff != 0:\r\n            return diff\r\n\r\n        # favor longest fqdn\r\n        return len(b) - len(a)\r\n\r\n    def _key_hostname(e):\r\n        # should never have a space in hostname\r\n        # favor hostnames w/o spaces\r\n        if ' ' in e:\r\n            first = 1\r\n        else:\r\n            first = -1\r\n\r\n        # punish localhost list\r\n        if e in punish:\r\n            second = punish.index(e)\r\n        else:\r\n            second = -1\r\n\r\n        # punish ipv6\r\n        third = e.count(':')\r\n\r\n        # punish ipv4\r\n        e_is_ipv4 = e.count('.') == 3 and not any(c.isalpha() for c in e)\r\n        if e_is_ipv4 and e.startswith('127.'):\r\n            fourth = 1\r\n        if e_is_ipv4:\r\n            fourth = 1\r\n        if not e_is_ipv4:\r\n            fourth = -1\r\n\r\n        # favor hosts with more dots\r\n        fifth = -(e.count('.'))\r\n\r\n        # favor longest fqdn\r\n        sixth = -(len(e))\r\n\r\n        return (first, second, third, fourth, fifth, sixth)\r\n\r\n    if how_to_sort == 'cmp':\r\n        return sorted(hostname_list, cmp=_cmp_hostname)\r\n    else:\r\n        return sorted(hostname_list, key=_key_hostname)\r\n\r\ndef comp(list1, list2, verbose):\r\n    if len(list1) != len(list2):\r\n        if verbose:\r\n            print(\"Lists are not the same length!\")\r\n        return False\r\n\r\n    is_same = True\r\n    for idx, val1 in enumerate(list1):\r\n        val2 = list2[idx]\r\n        if val1 != val2:\r\n            if verbose:\r\n                print(\"ERROR: list1[{0}] <{1}> != list2[{2}] <{3}>\".format(idx, val1, idx, val2))\r\n            is_same = False\r\n        else:\r\n            if verbose:\r\n                print(\"list1[{0}] <{1}> == list2[{2}] <{3}>\".format(idx, val1, idx, val2))\r\n    return is_same\r\n\r\nhosts = [\r\n    '128.187.10.3',\r\n    '128.187.20.5',\r\n    '192.168.1.1',\r\n    '192.168.2.1',\r\n    '1 2 3 4',\r\n    '1.2.3.4',\r\n    'www.1.com',\r\n    'www.2.com',\r\n    'www.a.com',\r\n    'www.b.com',\r\n    'www.one.com',\r\n    'www.two.com',\r\n    'www.alpha.com',\r\n    'www.beta.com',\r\n    'localhost.localdomain',\r\n    'localhost.my.domain',\r\n    'localhost4.localdomain4',\r\n    'localhost',\r\n    'ip6-localhost',\r\n    'ip6-loopback',\r\n    'ipv6-localhost',\r\n    'ipv6-loopback',\r\n    '127.0.2.1',\r\n    '127.0.1.1',\r\n    '127.0.0.1',\r\n    '0.0.0.0',\r\n    '::1',\r\n    'fe00::',\r\n    'fe02::',\r\n]\r\n\r\ncmp_sorted_hosts = _sort_hostnames(hosts, \"cmp\")\r\nkey_sorted_hosts = _sort_hostnames(hosts, \"key\")\r\n\r\nif comp(cmp_sorted_hosts, key_sorted_hosts, True):\r\n    print(\"cmp_sorted_hosts and key_sorted_hosts produce the same results.\")\r\nelse:\r\n    print(\"ERROR: cmp_sorted_hosts and key_sorted_hosts produce different results!\")\r\n</pre>"},"comment":{"url":"https://api.github.com/repos/saltstack/salt/issues/comments/100300275","html_url":"https://github.com/saltstack/salt/pull/23481#issuecomment-100300275","issue_url":"https://api.github.com/repos/saltstack/salt/issues/23481","id":100300275,"user":{"login":"ckochenower","id":3536804,"avatar_url":"https://avatars.githubusercontent.com/u/3536804?v=3","gravatar_id":"","url":"https://api.github.com/users/ckochenower","html_url":"https://github.com/ckochenower","followers_url":"https://api.github.com/users/ckochenower/followers","following_url":"https://api.github.com/users/ckochenower/following{/other_user}","gists_url":"https://api.github.com/users/ckochenower/gists{/gist_id}","starred_url":"https://api.github.com/users/ckochenower/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ckochenower/subscriptions","organizations_url":"https://api.github.com/users/ckochenower/orgs","repos_url":"https://api.github.com/users/ckochenower/repos","events_url":"https://api.github.com/users/ckochenower/events{/privacy}","received_events_url":"https://api.github.com/users/ckochenower/received_events","type":"User","site_admin":false},"created_at":"2015-05-08T17:07:21Z","updated_at":"2015-05-08T17:07:21Z","body":"@skizunov - I hope you don't mind, but I submitted a PR with your changes. \r\nhttps://github.com/saltstack/salt/pull/23500\r\nThank you for the code review. "}}