Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vmware.vmware_rest.vcenter_vm_info Fails in Python 3.10.2 #308

Closed
radiojosh opened this issue Feb 21, 2022 · 3 comments · Fixed by ansible-collections/cloud.common#102
Closed
Labels
bug This issue/PR relates to a bug p1

Comments

@radiojosh
Copy link

SUMMARY

When using the vmware.vmware_rest.vcenter_vm_info module, the module fails with a zipimporter error.

ISSUE TYPE
  • Bug Report
COMPONENT NAME

vmware.vmware_rest.vcenter_vm_info

ANSIBLE VERSION
ansible [core 2.12.2]
  config file = /home/joshua/.ansible.cfg
  configured module search path = ['/home/joshua/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/joshua/.pyenv/bin/versions/3.10.2/envs/ansible-old/lib/python3.10/site-packages/ansible
  ansible collection location = /home/joshua/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/joshua/.pyenv/bin/versions/ansible-old/bin/ansible
  python version = 3.10.2 (main, Feb  2 2022, 11:47:58) [GCC 9.3.0]
  jinja version = 3.0.3
  libyaml = True
COLLECTION VERSION
Collection         Version
------------------ -------
vmware.vmware_rest 2.1.4
CONFIGURATION
DEFAULT_HOST_LIST(/home/joshua/.ansible.cfg) = ['/home/joshua/git_repos/ansible_inventories/lml_production_inventory']
DEFAULT_KEEP_REMOTE_FILES(env: ANSIBLE_KEEP_REMOTE_FILES) = True
DEFAULT_ROLES_PATH(/home/joshua/.ansible.cfg) = ['/home/joshua/git_repos/ansible_roles', '/home/joshua/.ansible/roles', '/usr/share/ansible/roles', '/etc/ansible/roles']
DEFAULT_VAULT_ENCRYPT_IDENTITY(/home/joshua/.ansible.cfg) = home
DEFAULT_VAULT_IDENTITY(/home/joshua/.ansible.cfg) = home
DEFAULT_VAULT_ID_MATCH(/home/joshua/.ansible.cfg) = True
DEFAULT_VAULT_PASSWORD_FILE(/home/joshua/.ansible.cfg) = /home/joshua/.vaultpass
TAGS_SKIP(/home/joshua/.ansible.cfg) = ['deploy', 'disruptive']
OS / ENVIRONMENT

Ubuntu 20.04.4 LTS running in WSL2
Python 3.10.2 in a pyenv/virtualenv virtual environment

STEPS TO REPRODUCE

Activate your python virtual environment running Python 3.10.2
Upgrade Pip
Install Ansible
Install aiohttp
Execute your playbook

- name: Look up the VM in the inventory
  vmware.vmware_rest.vcenter_vm_info:
    vcenter_hostname: "<servername>"
    vcenter_username: "[email protected]"
    vcenter_password: "<password>"
    vcenter_validate_certs: no
    filter_names:
    - "<vmname>"
  delegate_to: localhost
  register: search_result

- name: debug
  debug:
    msg: "{{ search_result }}"
EXPECTED RESULTS

This is the output when running the playbook in Python 3.9.10:

TASK [lml_win_common_role : Look up the VM in the inventory] ***********************************************************************************************************************************************************************************************************
task path: /home/joshua/git_repos/ansible_roles/lml_win_common_role/tasks/main.yml:89
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: joshua
<localhost> EXEC /bin/sh -c 'echo ~joshua && sleep 0'
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/joshua/.ansible/tmp `"&& mkdir "` echo /home/joshua/.ansible/tmp/ansible-tmp-1645404663.008441-1625427-88833642033096 `" && echo ansible-tmp-1645404663.008441-1625427-88833642033096="` echo /home/joshua/.ansible/tmp/ansible-tmp-1645404663.008441-1625427-88833642033096 `" ) && sleep 0'
Loading collection cloud.common from /home/joshua/.ansible/collections/ansible_collections/cloud/common
Using module file /home/joshua/.ansible/collections/ansible_collections/vmware/vmware_rest/plugins/modules/vcenter_vm_info.py
<localhost> PUT /home/joshua/.ansible/tmp/ansible-local-16253896c9sh8p8/tmpxepzzvo6 TO /home/joshua/.ansible/tmp/ansible-tmp-1645404663.008441-1625427-88833642033096/AnsiballZ_vcenter_vm_info.py
<localhost> EXEC /bin/sh -c 'chmod u+x /home/joshua/.ansible/tmp/ansible-tmp-1645404663.008441-1625427-88833642033096/ /home/joshua/.ansible/tmp/ansible-tmp-1645404663.008441-1625427-88833642033096/AnsiballZ_vcenter_vm_info.py && sleep 0'
<localhost> EXEC /bin/sh -c '/home/joshua/.pyenv/bin/versions/3.9.10/envs/ansible-old2/bin/python3.9 /home/joshua/.ansible/tmp/ansible-tmp-1645404663.008441-1625427-88833642033096/AnsiballZ_vcenter_vm_info.py && sleep 0'
ok: [<inventory hostname> -> localhost] => {
    "changed": false,
    "invocation": {
        "module_args": {
            "clusters": null,
            "datacenters": null,
            "filter_names": [
                "<vmname>"
            ],
            "folders": null,
            "hosts": null,
            "names": [
                "<vmname>"
            ],
            "power_states": null,
            "resource_pools": null,
            "session_timeout": null,
            "vcenter_hostname": "<servername>",
            "vcenter_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "vcenter_rest_log_file": null,
            "vcenter_username": "[email protected]",
            "vcenter_validate_certs": false,
            "vm": null,
            "vms": null
        }
    },
    "value": [
        {
            "cpu_count": 4,
            "memory_size_MiB": 12288,
            "name": "<vmname>",
            "power_state": "POWERED_ON",
            "vm": "vm-23"
        }
    ]
}

TASK [lml_win_common_role : debug] *************************************************************************************************************************************************************************************************************************************
task path: /home/joshua/git_repos/ansible_roles/lml_win_common_role/tasks/main.yml:100
ok: [<inventory_hostname>] => {
    "msg": {
        "changed": false,
        "failed": false,
        "value": [
            {
                "cpu_count": 4,
                "memory_size_MiB": 12288,
                "name": "<vmname>",
                "power_state": "POWERED_ON",
                "vm": "vm-23"
            }
        ]
    }
}
META: role_complete for <inventory hostname>
META: ran handlers
META: ran handlers
ACTUAL RESULTS
TASK [lml_win_common_role : Look up the VM in the inventory] ***********************************************************************************************************************************************************************************************************
task path: /home/joshua/git_repos/ansible_roles/lml_win_common_role/tasks/main.yml:89
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: joshua
<localhost> EXEC /bin/sh -c 'echo ~joshua && sleep 0'
<localhost> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/joshua/.ansible/tmp `"&& mkdir "` echo /home/joshua/.ansible/tmp/ansible-tmp-1645404826.3392863-1626791-137444516612027 `" && echo ansible-tmp-1645404826.3392863-1626791-137444516612027="` echo /home/joshua/.ansible/tmp/ansible-tmp-1645404826.3392863-1626791-137444516612027 `" ) && sleep 0'
Loading collection cloud.common from /home/joshua/.ansible/collections/ansible_collections/cloud/common
Using module file /home/joshua/.ansible/collections/ansible_collections/vmware/vmware_rest/plugins/modules/vcenter_vm_info.py
<localhost> PUT /home/joshua/.ansible/tmp/ansible-local-1626742l5zvia69/tmphb_dzj33 TO /home/joshua/.ansible/tmp/ansible-tmp-1645404826.3392863-1626791-137444516612027/AnsiballZ_vcenter_vm_info.py
<localhost> EXEC /bin/sh -c 'chmod u+x /home/joshua/.ansible/tmp/ansible-tmp-1645404826.3392863-1626791-137444516612027/ /home/joshua/.ansible/tmp/ansible-tmp-1645404826.3392863-1626791-137444516612027/AnsiballZ_vcenter_vm_info.py && sleep 0'
<localhost> EXEC /bin/sh -c '/home/joshua/.pyenv/bin/versions/3.10.2/envs/ansible-old/bin/python3.10 /home/joshua/.ansible/tmp/ansible-tmp-1645404826.3392863-1626791-137444516612027/AnsiballZ_vcenter_vm_info.py && sleep 0'
fatal: [<inventory hostname> -> localhost]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "clusters": null,
            "datacenters": null,
            "filter_names": [
                "<vmname>"
            ],
            "folders": null,
            "hosts": null,
            "names": [
                "<vmname>"
            ],
            "power_states": null,
            "resource_pools": null,
            "session_timeout": null,
            "vcenter_hostname": "<servername>",
            "vcenter_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
            "vcenter_rest_log_file": null,
            "vcenter_username": "[email protected]",
            "vcenter_validate_certs": false,
            "vm": null,
            "vms": null
        }
    },
    "msg": [
        "  File \"/home/joshua/.pyenv/bin/versions/3.10.2/lib/python3.10/runpy.py\", line 196, in _run_module_as_main\n    return _run_code(code, main_globals, None,\n",
        "  File \"/home/joshua/.pyenv/bin/versions/3.10.2/lib/python3.10/runpy.py\", line 86, in _run_code\n    exec(code, run_globals)\n",
        "  File \"/tmp/ansible_vmware.vmware_rest.vcenter_vm_info_payload_31co90sp/ansible_vmware.vmware_rest.vcenter_vm_info_payload.zip/ansible_collections/cloud/common/plugins/module_utils/turbo/server.py\", line 383, in <module>\n",
        "  File \"/tmp/ansible_vmware.vmware_rest.vcenter_vm_info_payload_31co90sp/ansible_vmware.vmware_rest.vcenter_vm_info_payload.zip/ansible_collections/cloud/common/plugins/module_utils/turbo/server.py\", line 360, in start\n",
        "  File \"/home/joshua/.pyenv/bin/versions/3.10.2/lib/python3.10/asyncio/base_events.py\", line 595, in run_forever\n    self._run_once()\n",
        "  File \"/home/joshua/.pyenv/bin/versions/3.10.2/lib/python3.10/asyncio/base_events.py\", line 1881, in _run_once\n    handle._run()\n",
        "  File \"/home/joshua/.pyenv/bin/versions/3.10.2/lib/python3.10/asyncio/events.py\", line 80, in _run\n    self._context.run(self._callback, *self._args)\n",
        "  File \"/tmp/ansible_vmware.vmware_rest.vcenter_vm_info_payload_31co90sp/ansible_vmware.vmware_rest.vcenter_vm_info_payload.zip/ansible_collections/cloud/common/plugins/module_utils/turbo/server.py\", line 331, in handle\n",
        "  File \"/tmp/ansible_vmware.vmware_rest.vcenter_vm_info_payload_31co90sp/ansible_vmware.vmware_rest.vcenter_vm_info_payload.zip/ansible_collections/cloud/common/plugins/module_utils/turbo/server.py\", line 301, in run_as_module\n",
        "zipimporter.find_spec() takes from 2 to 3 positional arguments but 4 were given"
    ]
}
@goneri
Copy link
Member

goneri commented Feb 21, 2022

Thank you for the bug report.

@goneri goneri added the bug This issue/PR relates to a bug label Feb 21, 2022
@gravesm
Copy link
Member

gravesm commented Feb 22, 2022

I just tested locally (with kubernetes.core), and ansible-collections/cloud.common#102 appears to solve this problem.

@Plasticjesus
Copy link

I am also running into this bug. Fedora 35, ansible 2.9.27 and Python 3.10.3.

softwarefactory-project-zuul bot pushed a commit to ansible-collections/cloud.common that referenced this issue Apr 11, 2022
Fix import logic trying to load incorrect module

SUMMARY

There is currently a bug in the import logic that manifests when two
Ansible modules try to load a python module from somewhere in
module_utils. If the python module shares the same name as the second
(or subsequent) Ansible modules, turbo mode will attempt to load the
python module instead of the Ansible module.
This fixes the logic by removing the meta_path modification, which was
the root of the problem. Instead, it keeps the sys.path modification as
before, but attempts to reload any ansible_collection package modules.
We can't reload every module as this would overwrite any shared state in
the module cache, defeating the whole purpose of turbo mode. By
reloading only the package modules we force it to reexamine its
contents, which should now be pointing to the new zip archive since we
changed the loader for the package module.
One caveat to this is that if shared state were being stored in a
package's __init__.py, it would be written over from the package reload.
This can be worked around by conditionally defining any shared state in
__init__.py with something like:
    try:
        state
    except NameError:
        state = {}

ISSUE TYPE


Bugfix Pull Request

COMPONENT NAME

ADDITIONAL INFORMATION


The underlying problem with the current implementation is that it adds a zipimporter to the meta_path for every loaded module in the ansible_collections namespace. A zipimporter only examines that last portion of the module fullname. This can be seen in a simple example. Assuming a zip archive that looks like:
foo.zip
|-- foo
    |-- __init__.py
    |-- a
    |   |-- __init__.py
    |   |-- c.py
    |-- b
        |-- __init__.py
        |-- c.py

Now, create a zipimporter for this, pointing to a subpackage and see that it loads the wrong module:
loader = zipimporter("foo.zip/foo/a")
mod = loader.load_module("foo.b.c")
assert mod.__name__ == "foo.b.c"  # True
assert mod.__file__ == "foo.zip/foo/a/c.py"  # True
By adding zipimporters to the meta_path, when we try to load a module that isn't yet in the module cache, we add finders that will load the wrong module. This is a problem, for example, when you have a collection that looks like:
collection/plugins/modules/a.py
collection/plugins/modules/b.py
collection/plugins/module_utils/b.py

If modules a and b both import b from module_utils, a playbook that first runs module a and then runs module b will fail with an error that it can't find main(), because in the second task it has loaded module_utils/b.py instead of modules/b.py.
It's not enough to just change the __path__ on a package module that has already been imported. This is only examined the first time a package is loaded. When the package is loaded any modules inside __path__ are added as attributes to the package module. Subsequent attempts to access a new module in that package that isn't in the module cache will fail because it is only checking package's current attributes. We have to reload the package modules to be able to load any new modules that may now be in that package namespace. As stated above, this does mean any shared state held on the package (__init__.py) will be overwritten.
I can't see a perfect solution for this, but I think this change is the best one.



Closes: ansible-collections/vmware.vmware_rest#308

Reviewed-by: Gonéri Le Bouder <[email protected]>
Reviewed-by: None <None>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue/PR relates to a bug p1
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants