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

Support for ansible collections import hook #652

Closed
cognifloyd opened this issue Oct 6, 2019 · 27 comments · Fixed by #715
Closed

Support for ansible collections import hook #652

cognifloyd opened this issue Oct 6, 2019 · 27 comments · Fixed by #715

Comments

@cognifloyd
Copy link
Contributor

cognifloyd commented Oct 6, 2019

Ansible 2.8.4 + mitogen 0.2.8
Collections installed in ~/.ansible/collections
Playbook and all roles are in a collection.

It looks like the mitogen linear_strategy is not compatible with the collections loader.

Sorry I'm working remotely (to test mitogen with this playbook) on my phone so all I can provide are screenshots of the error. I can look up more details next week.

Screenshot_20191005-191340~4

Screenshot_20191005-191941~2

@dw
Copy link
Member

dw commented Oct 19, 2019

Hi Jacob!

For collections they have implemented a Python import hook. Mitogen needs a little massaging to work for it. I'm busy on another project just now but will come to this real soon, it's a fairly straightforward fix

Thanks for reporting, and apologies for the huge delay

@cognifloyd
Copy link
Contributor Author

cognifloyd commented Oct 25, 2019

When I took the traceback screenshot, I picked the portion that shows Ansible's PEP 451 import loader for collections. I figured it was a conflict in how that import loader and the mitogen import system worked.

Do you need any more details to diagnose this?

@cognifloyd
Copy link
Contributor Author

cognifloyd commented Oct 25, 2019

Oh, and no problem on the delay. I have been quite swamped as well!

That said, I am very much looking forward to speeding up a playbook using mitogen. It is in a collection and the entire playbook runs with connection: local as it generates things on behalf of the target host, but only needs to save them locally on the controller. Mitogen's eliminating the python startup time for the repeated modules (copy, template, synchronize, and shell) is my best hope for bringing runtime down.

@mkobel
Copy link

mkobel commented Nov 13, 2019

I get the same error using mitogen 0.2.9 and ansible 2.9.0, it also occurs without connection: local

Output:

ERROR! Unexpected Exception, this is probably a bug: module 'ansible_collections.ansible.builtin.plugins.action' has no attribute 'ActionBase'

the full traceback was:

Traceback (most recent call last):
  File "/usr/bin/ansible-playbook", line 123, in <module>
    exit_code = cli.run()
  File "/usr/lib/python3.7/site-packages/ansible/cli/playbook.py", line 127, in run
    results = pbex.run()
  File "/usr/lib/python3.7/site-packages/ansible/executor/playbook_executor.py", line 169, in run
    result = self._tqm.run(play=play)
  File "/usr/lib/python3.7/site-packages/ansible/executor/task_queue_manager.py", line 219, in run
    strategy = strategy_loader.get(new_play.strategy, self)
  File "/usr/lib/python3.7/site-packages/ansible/plugins/loader.py", line 552, in get
    self._module_cache[path] = self._load_module_source(name, path)
  File "/usr/lib/python3.7/site-packages/ansible/plugins/loader.py", line 525, in _load_module_source
    spec.loader.exec_module(module)
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/home/moritz/git/ansible/strategy_plugins/mitogen-0.2.9/ansible_mitogen/plugins/strategy/mitogen_linear.py", line 56, in <module>
    import ansible_mitogen.strategy
  File "/home/moritz/git/ansible/strategy_plugins/mitogen-0.2.9/ansible_mitogen/strategy.py", line 43, in <module>
    import ansible_mitogen.mixins
  File "/home/moritz/git/ansible/strategy_plugins/mitogen-0.2.9/ansible_mitogen/mixins.py", line 67, in <module>
    class ActionModuleMixin(ansible.plugins.action.ActionBase):
AttributeError: module 'ansible_collections.ansible.builtin.plugins.action' has no attribute 'ActionBase'

@cognifloyd
Copy link
Contributor Author

@dw any luck with supporting the collections import hook?

@cognifloyd cognifloyd changed the title Incompatible with collections? Support for ansible collections import hook Dec 4, 2019
@cognifloyd
Copy link
Contributor Author

cognifloyd commented Dec 6, 2019

So would the import hook need to be addressed here?
https://github.com/dw/mitogen/blob/d6329f3446af7b120b29656d285d740fdb738ac8/ansible_mitogen/strategy.py#L122

Would the module loader and other plugin type leaders also need to be wrapped? Collections can include any type of plugin (afaik - there may be a few exceptions but I'm not sure).

I see an import hook here, would that need to be adjusted?

I'm also looking at the PluginLoader in ansible to figure this out. I see use of imp for older python and importlib for newer python. I see collection_list and various bits that load plugins from collections.

Can this be addressed directly in ansible_mitogen or will the fix need to touch mitogen internals as well? Maybe in mitogen.core?

@cognifloyd
Copy link
Contributor Author

@s1113950 You said you're looking at adding support for Ansible 2.9 in #323.
Could you take a look at adding this 2.8 feature (collections) or help me figure out how to do so? I'm a little lost as to where adding collections support would need to be (ansible_mitogen vs other mitogen internals).

I'd love to use mitogen, but I depend on collections support so I haven't been able to test any of my playbooks with mitogen yet.

@s1113950
Copy link
Collaborator

Sure yeah I can take a look :)
Do you have a basic playbook that can reproduce the error? I haven't used collections through Ansible before.
What also helped me with interpreter discovery was finding out the PR(s) that landed the feature I was trying to add in Mitogen. I'll try and see if I can find what code added the feature on the Ansible side.

@cognifloyd
Copy link
Contributor Author

cognifloyd commented Mar 21, 2020

Sure, I'll look up the PRs, get a sample playbook, and post some additional background on collections later today. :) thank you

edit: add todo list

  • add background and resources
  • post a simple sample playbook + collection

@cognifloyd
Copy link
Contributor Author

FYI: A lot of work for developing collections actually happened in (an)other repo(s). So if you're looking for older info, you'll want to search for "mazer" which was a fork of the ansible-galaxy tool, but the changes have since been merged back into ansible-galaxy. Again, I'll pull together good resources for you later.

@cognifloyd
Copy link
Contributor Author

cognifloyd commented Mar 21, 2020

The plan is to move most modules/plugins (esp community maintained modules and network device plugins) out of ansible. Instead of batteries included, you will need to use various content collections that have their own release schedule separate from ansible itself.

FQCN = Fully Qualified Collection Name
FQRN = Fully Qualified Role Name (includes the collection)

Background

Official docs

These link to devel version as most up-to-date. Some of this used to be in the ansible-galaxy docs, but since 2.8 they've been moving to docs for ansible itself

In Ansible's Code

Primary parts of ansible core code:

Ansible code changes:
Mazer was a fork of ansible-galaxy to develop the concept of "collections". There was also a branch of ansible that added mazer support. Once the concept of collections was fleshed out, the ansible branch got merged into devel, mazer's collections code was merged back into ansible-galaxy, and mazer was deprecated.

Collection Contents

Collections can contain roles, modules, actions, and many (in 2.10 all) other plugin types. Playbooks can be included in collections, but the clean support for auto finding them without an absolute path is not baked into ansible yet.

target host-side plugin types that mitogen will probably need to handle in collections:

All controller-side plugin types can be in a collection with a few caveats (not sure which of these mitogen needs to handle or integrate with):

  • fully supported in collections (by frequency/type of plugin):
    • less-common but mitogen definitely touches these:
    • common:
      • action
      • callback
      • inventory
    • common Jinja context (These Jinja context plugins must always use FQCN in playbooks as there is no way to tell what the collections lookup path is since vars don't have metadata on where they are defined):
      • filter
      • test
      • lookup
    • network-device related plugins:
      • cliconf
      • netconf
      • httpapi (httpapi is not strictly only for network devices but was written for them)
    • less-common:
      • terminal
      • shell
      • become (still needs mitogen support, not just inside collections)
  • supported in collections with caveats (by frequency):
    • less-common:
      • cache (cache plugins are not supported yet for inventory plugin caches, only for core ansible caches like the fact cache)
    • rare:
      • vars (only in ansible 2.10+)
  • supported in collections but not used at playbook/adhoc runtime:
    • doc_fragments

The draft collection shows the layout of a collection with dirs for a lot of plugin types.

@cognifloyd
Copy link
Contributor Author

cognifloyd commented Mar 21, 2020

Here's a simple playbook + collection where the collection includes roles and a module:
https://github.com/newswangerd/collection_demo
Cool. The author has a YouTube overview of the collection_demo as well: https://www.youtube.com/watch?v=d792W44I5KM
Where he talks about mazer ... replace that with ansible-galaxy collection ...

@cognifloyd
Copy link
Contributor Author

Here's a collection (for debugging collections lol) that has several kinds of plugins:
https://galaxy.ansible.com/alikins/collection_inspect
It includes sample playbooks, a role, 3 modules, and one of each of these plugin types:

  • action
  • callback
  • filter
  • lookup
  • module_utils
  • modules
  • vars

@s1113950
Copy link
Collaborator

@cognifloyd thanks for all the info!
I'll give it a detailed look tomorrow PST. At first glance there might be some work that needs to be done in strategy.py, loaders.py, core.py, master.py, and connection.py.

@s1113950
Copy link
Collaborator

s1113950 commented Mar 23, 2020

(.venv) steven@Stevens-MBP:~/repos/mitogen-test$ ansible-galaxy collection install alikins.collection_inspect
- downloading role 'collection', owned by
[WARNING]: - collection was NOT installed successfully: Content has no field named 'owner'

ERROR! - you can use --ignore-errors to skip failed roles and finish processing the list.

@cognifloyd I'm probably doing something wrong 🤔 This is with ansible 2.8.8. The link you posted https://galaxy.ansible.com/alikins/collection_inspect said that it's only supported in ansible 2.9+ but you mentioned it was also backported to 2.8.3 so I thought it would work with 2.8.8 as well? How do I install a collection in 2.8?

@s1113950
Copy link
Collaborator

Or is it better to just get it to work in 2.9?

@cognifloyd
Copy link
Contributor Author

doh. I forgot about that. ansible-galaxy didn't get support for installing collections until 2.9. I started using collections with 2.8, but to install them I had a separate virtualenv with the devel version of ansible that I only used for installing the collection.

Yeah, skip 2.8 for now since we can't really install collections with it.

@cognifloyd
Copy link
Contributor Author

So, I guess that means get everything else working with 2.9 before tackling collections?

@s1113950
Copy link
Collaborator

Makes sense to me yeah 👍
I'm gonna update Mitogen's tests with ansible 2.9 and go from there 😄

@s1113950
Copy link
Collaborator

s1113950 commented May 8, 2020

Gonna try collections now

@s1113950
Copy link
Collaborator

s1113950 commented May 8, 2020

@cognifloyd I found something interesting: I didn't get the actionbase error when I ran the entire collection example from the playbook level, but I did when running it from inside a role at the task level.

ansible-playbook -b plays/collections.yml worked for me when the collections.yml was this:

- name: Test alikins.collection_inspect vars plugin
  hosts: localhost
  gather_facts: false
  vars:
    foo: "fooFooFOO"
    # Test having the filter plugin loaded early
    blip: "{{ foo |alikins.collection_inspect.collection_inspect }}"
  tasks:
    - name: "Test collection_inspect module and module_utils loaded from alikins.collection_inspect collection"
      alikins.collection_inspect.collection_inspect:
        args:
      register: module_result

    - name: Show collection_inspect module results
      debug:
        var: module_result

    # TODO: not sure if vars plugins are expected to work yet
    - name: show vars plugins for collection_inspect
      debug:
        msg: "{{ vars|to_nice_json }}"

I'm gonna run it from the task-level now and get it working there!

@s1113950
Copy link
Collaborator

s1113950 commented May 8, 2020

I don't get the error on the task-level when using:
Mitogen: master
Ansible: 2.9.6
collection installed via: .venv/bin/ansible-galaxy collection install alikins.collection_inspect.

Here's the playbook:

- hosts: node
  gather_facts: false
  vars:
    foo: "fooFooFOO"
    # Test having the filter plugin loaded early
    blip: "{{ foo |alikins.collection_inspect.collection_inspect }}"
  roles:
  - run_test

and here's the role:

- name: "Test collection_inspect module and module_utils loaded from alikins.collection_inspect collection"
  alikins.collection_inspect.collection_inspect:
    args:
  register: module_result

- name: Show collection_inspect module results
  debug:
    var: module_result

# TODO: not sure if vars plugins are expected to work yet
- name: show vars plugins for collection_inspect
  debug:
    msg: "{{ vars|to_nice_json }}"

Previously I got the actionbase error when I hadn't removed the following from the original example:
tasks
vars
gather_facts
hosts

I moved them to be declared in the playbook, and then had the playbook run the example there and it worked. @cognifloyd is that not how people run collections? 🤔

@s1113950
Copy link
Collaborator

s1113950 commented May 8, 2020

I'll continue trying the other playbooks here: https://github.com/alikins/collection_inspect/tree/master/playbooks

@s1113950
Copy link
Collaborator

s1113950 commented May 8, 2020

aHa! Finally able to repro at the task level. Sweet

@s1113950
Copy link
Collaborator

s1113950 commented May 8, 2020

The full traceback is:
Traceback (most recent call last):
  File "master:/private/tmp/mitogen/ansible_mitogen/runner.py", line 975, in _run
    self._run_code(code, mod)
  File "master:/private/tmp/mitogen/ansible_mitogen/runner.py", line 941, in _run_code
    exec('exec code in vars(mod)')
  File "<string>", line 1, in <module>
  File "master:/Users/steven/.ansible/collections/ansible_collections/alikins/collection_inspect/plugins/modules/get_collection_inspect.py", line 24, in <module>
ImportError: No module named alikins.collection_inspect.plugins.module_utils.collection_inspect
[WARNING]: Platform darwin on host localhost is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter
could change this. See https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
fatal: [localhost]: FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "module_stderr": "Traceback (most recent call last):\n  File \"master:/private/tmp/mitogen/ansible_mitogen/runner.py\", line 975, in _run\n    self._run_code(
code, mod)\n  File \"master:/private/tmp/mitogen/ansible_mitogen/runner.py\", line 941, in _run_code\n    exec('exec code in vars(mod)')\n  File \"<string>\", lin
e 1, in <module>\n  File \"master:/Users/steven/.ansible/collections/ansible_collections/alikins/collection_inspect/plugins/modules/get_collection_inspect.py\", l
ine 24, in <module>\nImportError: No module named alikins.collection_inspect.plugins.module_utils.collection_inspect\n",
    "module_stdout": "",
    "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
    "rc": 1
}

currently stuff loads, sorta. Submodules of ansible_collections are empty so the parent has nothing to send to the child in this case.

It has to do with the quite hacky way that collections are assembled (they're effectively a fake module with no real __file__ (it's called __synthetic__ 😂 ). The collections loader also has no nice way of generating source code so the parent is having a hard time sending stuff to the child on import. I think I'm close though 🤔 It might involve a bit of reverse-engineering the loader to "unload" the source on demand, but I have a few other things to try before it gets to that point.

@nitzmahone
Copy link

nitzmahone commented May 14, 2020

Just a hint: the Python loader underwent a significant rewrite for 2.10 in ansible/ansible#67684 - largely to make it more "Python standards-compliant". That said, we purposely don't "Python load" module/module_util code during Ansible module construction, because that causes the Python module global code to execute in the controller (which can have ... interesting side effects, depending on the module). Unfortunately, pkgutil.get_data still runs package init code (there's an open proposal to extend it not to do so, but alas), so we can't rely exclusively on the Python standard mechanisms for accessing blob data in collections. I could blacklist those locations in the Python loader so they can't run package init code, but that would prevent legit package init code from running for module_utils that are used by controller plugins. So there will likely be a non-importing version of pkgutil.get_data in 2.10... I'd be careful about relying on any of the implementation details of the module_utils location/construction (eg CollectionModuleInfo I saw mentioned in the collections support PR)- those have changed quite a bit, and will probably continue to.

@haojue
Copy link

haojue commented Feb 4, 2024

Hi folks,
I ran into the same error as in the description, I'm using ansible 2.9.27(not use 2.10.* in short term) and mitogen 0.2.*, what would be your suggestion for fixing it? just patch .py under ansible_mitogen with these in #715 ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants