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

[WIP]Make cloud providers dynamic #15537

Draft
wants to merge 95 commits into
base: devel
Choose a base branch
from

Conversation

djyasin
Copy link
Member

@djyasin djyasin commented Sep 17, 2024

SUMMARY
ISSUE TYPE
  • New or Enhanced Feature
COMPONENT NAME
  • API
AWX VERSION
awx: 24.6.2
ADDITIONAL INFORMATION

@djyasin djyasin marked this pull request as draft September 17, 2024 13:29
@djyasin djyasin changed the title Make cloud providers dynamic 28722 [WIP]Make cloud providers dynamic Sep 17, 2024
awx/main/constants.py Outdated Show resolved Hide resolved
@webknjaz
Copy link
Member

Let's untangle this ball of yarn...

PYTHONDONTWRITEBYTECODE=1 py.test -p no:cacheprovider --create-db --cov --cov-report=xml --junitxml=reports/junit.xml awx/main/tests/unit awx/main/tests/functional awx/conf/tests awx/sso/tests

So this is what's failing ^

And below is a traceback chain:

Traceback (most recent call last):
  File "/usr/lib64/python3.11/logging/config.py", line 400, in resolve
    found = getattr(found, frag)
            ^^^^^^^^^^^^^^^^^^^^
AttributeError: module 'awx.main' has no attribute 'utils'

This one is not fully clear where it originates. Perhaps, it's pytest's collection stage.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib64/python3.11/logging/config.py", line 552, in configure
    formatters[name] = self.configure_formatter(
                       ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.11/logging/config.py", line 664, in configure_formatter
    result = self.configure_custom(config)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.11/logging/config.py", line 479, in configure_custom
    c = self.resolve(c)
        ^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.11/logging/config.py", line 402, in resolve
    self.importer(used)
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/assertion/rewrite.py", line 184, in exec_module
    exec(co, module.__dict__)
  File "/awx_devel/awx/main/utils/__init__.py", line 14, in <module>
    from awx.main.utils.licensing import get_licenser  # noqa
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/assertion/rewrite.py", line 184, in exec_module
    exec(co, module.__dict__)
  File "/awx_devel/awx/main/utils/licensing.py", line 38, in <module>
    from awx.main.constants import SUBSCRIPTION_USAGE_MODEL_UNIQUE_HOSTS
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/assertion/rewrite.py", line 184, in exec_module
    exec(co, module.__dict__)
  File "/awx_devel/awx/main/constants.py", line 8, in <module>
    from awx.main.models.inventory import InventorySourceOptions
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/assertion/rewrite.py", line 184, in exec_module
    exec(co, module.__dict__)
  File "/awx_devel/awx/main/models/__init__.py", line 12, in <module>
    from ansible_base.resource_registry.fields import AnsibleResourceField
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/assertion/rewrite.py", line 184, in exec_module
    exec(co, module.__dict__)
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/ansible_base/resource_registry/fields.py", line 1, in <module>
    from django.contrib.contenttypes.models import ContentType
  File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/assertion/rewrite.py", line 184, in exec_module
    exec(co, module.__dict__)
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/django/contrib/contenttypes/models.py", line 139, in <module>
    class ContentType(models.Model):
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/django/db/models/base.py", line 129, in __new__
    app_config = apps.get_containing_app_config(module)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/django/apps/registry.py", line 260, in get_containing_app_config
    self.check_apps_ready()
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/django/apps/registry.py", line 138, in check_apps_ready
    raise AppRegistryNotReady("Apps aren't loaded yet.")
django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.

This is an import happening before the django things got initialized ^

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/var/lib/awx/venv/awx/bin/py.test", line 8, in <module>
    sys.exit(console_main())
             ^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/config/__init__.py", line 201, in console_main
    code = main()
           ^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/config/__init__.py", line 156, in main
    config = _prepareconfig(args, plugins)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/config/__init__.py", line 341, in _prepareconfig
    config = pluginmanager.hook.pytest_cmdline_parse(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/helpconfig.py", line 105, in pytest_cmdline_parse
    config = yield
             ^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/config/__init__.py", line 1140, in pytest_cmdline_parse
    self.parse(args)
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/config/__init__.py", line 1494, in parse
    self._preparse(args, addopts=addopts)
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/config/__init__.py", line 1398, in _preparse
    self.hook.pytest_load_initial_conftests(
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pluggy/_hooks.py", line 513, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pluggy/_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pluggy/_callers.py", line 139, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/warnings.py", line 151, in pytest_load_initial_conftests
    return (yield)
            ^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pluggy/_callers.py", line 122, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/_pytest/capture.py", line 154, in pytest_load_initial_conftests
    yield
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pluggy/_callers.py", line 103, in _multicall
    res = hook_impl.function(*args)
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pytest_django/plugin.py", line 361, in pytest_load_initial_conftests
    _setup_django(early_config)
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/pytest_django/plugin.py", line 237, in _setup_django
    django.setup()
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/django/__init__.py", line 19, in setup
    configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
  File "/var/lib/awx/venv/awx/lib64/python3.11/site-packages/django/utils/log.py", line 76, in configure_logging
    logging_config_func(logging_settings)
  File "/usr/lib64/python3.11/logging/config.py", line 823, in dictConfig
    dictConfigClass(config).configure()
  File "/usr/lib64/python3.11/logging/config.py", line 555, in configure
    raise ValueError('Unable to configure '
ValueError: Unable to configure formatter 'json'

And this is the last one that cascaded from the previous two ^

I see pytest-django in the path and that's what's likely attempting to load things early...

I think that before this new import from awx.main.models.inventory import InventorySourceOptions, the respective code path wasn't being hit and the imports happened to be in the right order in tests.

awx/main/apps.py Outdated Show resolved Hide resolved
awx/main/constants.py Outdated Show resolved Hide resolved
awx/main/constants.py Outdated Show resolved Hide resolved
awx/main/constants.py Outdated Show resolved Hide resolved
awx/main/apps.py Outdated Show resolved Hide resolved
awx/api/serializers.py Outdated Show resolved Hide resolved
awx/main/apps.py Outdated Show resolved Hide resolved
awx/main/utils/plugins.py Outdated Show resolved Hide resolved
awx/main/utils/plugins.py Outdated Show resolved Hide resolved
awx/main/utils/plugins.py Outdated Show resolved Hide resolved
awx/api/serializers.py Outdated Show resolved Hide resolved
awx/main/apps.py Outdated Show resolved Hide resolved
awx/main/models/base.py Outdated Show resolved Hide resolved
awx/main/utils/plugins.py Outdated Show resolved Hide resolved
djyasin and others added 17 commits October 15, 2024 11:49
Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) <[email protected]>
Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) <[email protected]>
@djyasin djyasin force-pushed the make_cloud_providers_dynamic_28722 branch from 22f1f70 to 2a5875e Compare October 15, 2024 15:50
Copy link

sonarcloud bot commented Oct 16, 2024

@@ -2482,6 +2490,7 @@ def get_field_from_model_or_attrs(fd):
else:
redundant_scm_fields = list(filter(lambda x: attrs.get(x, None), ['source_project', 'source_path', 'scm_branch']))
if redundant_scm_fields:
breakpoint()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FIXME

Suggested change
breakpoint()

@@ -2300,6 +2301,7 @@ class Meta:

class InventorySourceOptionsSerializer(BaseSerializer):
credential = DeprecatedCredentialField(help_text=_('Cloud credential to use for inventory updates.'))
# source = serializers.ChoiceField(choices=[])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this work?

:returns: Dictionary of plugin cloud names plus source control.
:rtype: dict[str, str]
"""
# Get the list of plugin names
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's drop the comments before merging. They shouldn't repeat the code. If code is unobvious, rename functions and variables to make it relay the same message as this comment.

Comment on lines +36 to +40
# Create a dictionary of plugin names
cloud_sources = {plugin: plugin for plugin in plugins}
cloud_sources['scm'] = 'Source Control Management'

return cloud_sources # Return the complete dictionary
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's compose the dict in one go:

Suggested change
# Create a dictionary of plugin names
cloud_sources = {plugin: plugin for plugin in plugins}
cloud_sources['scm'] = 'Source Control Management'
return cloud_sources # Return the complete dictionary
return dict(zip(plugins, plugins), scm='Source Control Management')

return list(InventorySourceOptions.injectors.keys())


def compute_cloud_inventory_sources() -> dict[str, str]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing that it's called in several places, let's cache this function's return value too:

Suggested change
def compute_cloud_inventory_sources() -> dict[str, str]:
@cache
def compute_cloud_inventory_sources() -> dict[str, str]:



@pytest.fixture
def fixture_compute_cloud_inventory_sources():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to use “fixture” in fixture names. It's common to use the produced thing name, as that's what's injected into the tests. Additionally, fixtures should have docstrings for pytest --collect-only --fixtures.

Suggested change
def fixture_compute_cloud_inventory_sources():
def computed_cloud_inventory_sources() -> dict[str, str]:
"""A mapping of cloud inventory source names to labels."""

Comment on lines +372 to +375
else:
raise ValueError("No inventories found in the queryset")
else:
raise ValueError("No available cloud providers found")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's reduce the indentation where possible:

Suggested change
else:
raise ValueError("No inventories found in the queryset")
else:
raise ValueError("No available cloud providers found")
raise ValueError("No inventories found in the queryset")
raise ValueError("No available cloud providers found")

@@ -609,23 +609,9 @@ def test_get_constructed_inventory(self, constructed_inventory, admin_user, get)
r = get(url=reverse('api:constructed_inventory_detail', kwargs={'pk': constructed_inventory.pk}), user=admin_user, expect=200)
assert r.data['update_cache_timeout'] == 53

def test_patch_constructed_inventory(self, constructed_inventory, admin_user, patch):
def test_patch_constructed_inventory_generated_source_limits_editable_fields(self, constructed_inventory, admin_user, project, patch, inventory):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the unused arguments, inject those fixtures differently:

Suggested change
def test_patch_constructed_inventory_generated_source_limits_editable_fields(self, constructed_inventory, admin_user, project, patch, inventory):
@pytest.mark.usefixtures('inventory', 'project')
def test_patch_constructed_inventory_generated_source_limits_editable_fields(self, constructed_inventory, admin_user, patch) -> None:

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

Successfully merging this pull request may close these issues.

3 participants