1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-02-04 07:51:50 +00:00

lxd_storage_pool_info, lxd_storage_volume_info: new modules (#11198)

* Fix mistaken rebase

* plugins/modules/lxd_storage_: include error codes, clean up notes

* plugins/modules/lxd_storage_: snap_url, ruff fix

* plugins/modules/lxd_storage_volume_info.py: remove checks on expected api returned bits

* plugins/modules/lxd_storage_volume_info.py: required: true

* tests/integration/targets/lxd_storage_volume_info/tasks/main.yaml: add Test fetching specific volume by name

* tests/unit/plugins/modules/test_lxd_storage_: add unit tests

* tests/integration/targets/lxd_storage_pool_info/tasks/main.yaml: add integratio tests

* tests/integration/targets/lxd_storage_: not required

* tests/integration/targets/lxd_storage_: not required perhaps, lxd_project has them

* tests/unit/plugins/modules/test_lxd_storage_volume_info.py: fix python3.8 tests

* tests/unit/plugins/modules/test_lxd_storage_pool_info.py: fix python3.8

* tests/integration/targets/lxd_storage_: correct paths for aliases

* tests/unit/plugins/modules/test_lxd_storage_volume_info.py: remove backticks

* tests/unit/plugins/modules/test_lxd_storage_volume_info.py: remove blank line

* tests/unit/plugins/modules/test_lxd_storage_: python3.8 changes

* tests/unit/plugins/modules/test_lxd_storage_: python3.8 changes

* tests/unit/plugins/lookup/test_github_app_access_token.py: restore

* tests/unit/plugins/connection/test_wsl.py: restore

* plugins/modules/lxd_storage_: use ANSIBLE_LXD_DEFAULT_SNAP_URL and put API version into const

* lxd_storage_volume_info: use recursion to gather all volume details

* tests/integration/targets/lxd_storage_volume_info/tasks/main.yaml: fix silet skipped failures

* tests/integration/targets/lxd_storage_pool_info/tasks/main.yaml: fix silet failures

* lxd_storage_pool_info: update to use recursion to gather all details in one shot

* Remove unnecessary change.

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
Sean McAvoy 2025-12-01 02:58:45 -03:00 committed by GitHub
parent 16d51a8233
commit 6365b5a981
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1243 additions and 0 deletions

View file

@ -0,0 +1,125 @@
# Copyright (c) 2025, Sean McAvoy (@smcavoy) <seanmcavoy@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import annotations
from unittest.mock import patch
from ansible_collections.community.general.plugins.modules import lxd_storage_pool_info as module
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import (
AnsibleExitJson,
AnsibleFailJson,
ModuleTestCase,
set_module_args,
)
class FakeLXDClient:
responses: dict[tuple[str, str], dict] = {}
def __init__(self, url, key_file=None, cert_file=None, debug=False, **kwargs):
self.url = url
self.key_file = key_file
self.cert_file = cert_file
self.debug = debug
self.logs = [{"type": "fake-request"}] if debug else []
def authenticate(self, trust_password):
self.trust_password = trust_password
def do(self, method, url, ok_error_codes=None, **kwargs):
try:
return self.responses[(method, url)]
except KeyError as exc:
raise AssertionError(f"Unexpected call: {method} {url}") from exc
class TestLXDStoragePoolInfo(ModuleTestCase):
def setUp(self):
super().setUp()
self.module = module
def test_returns_storage_pools(self):
"""Pool metadata from the API is returned unchanged using recursion."""
FakeLXDClient.responses = {
("GET", "/1.0/storage-pools?recursion=1"): {
"type": "sync",
"metadata": [
{"name": "default", "driver": "dir"},
{"name": "fast", "driver": "zfs"},
],
},
}
with patch.object(self.module, "LXDClient", FakeLXDClient):
with patch.object(self.module.os.path, "exists", return_value=False):
with self.assertRaises(AnsibleExitJson) as exc:
with set_module_args({}):
self.module.main()
result = exc.exception.args[0]
assert result["storage_pools"] == [
{"name": "default", "driver": "dir"},
{"name": "fast", "driver": "zfs"},
]
def test_returns_specific_storage_pool(self):
"""When name is specified, only that pool is returned."""
FakeLXDClient.responses = {
("GET", "/1.0/storage-pools/default"): {"type": "sync", "metadata": {"name": "default", "driver": "dir"}},
}
with patch.object(self.module, "LXDClient", FakeLXDClient):
with patch.object(self.module.os.path, "exists", return_value=False):
with self.assertRaises(AnsibleExitJson) as exc:
with set_module_args({"name": "default"}):
self.module.main()
result = exc.exception.args[0]
assert result["storage_pools"] == [
{"name": "default", "driver": "dir"},
]
def test_filters_storage_pools_by_type(self):
"""Pools can be filtered by driver type using recursion."""
FakeLXDClient.responses = {
("GET", "/1.0/storage-pools?recursion=1"): {
"type": "sync",
"metadata": [
{"name": "default", "driver": "dir"},
{"name": "fast", "driver": "zfs"},
],
},
}
with patch.object(self.module, "LXDClient", FakeLXDClient):
with patch.object(self.module.os.path, "exists", return_value=False):
with self.assertRaises(AnsibleExitJson) as exc:
with set_module_args({"type": ["zfs"]}):
self.module.main()
result = exc.exception.args[0]
assert result["storage_pools"] == [
{"name": "fast", "driver": "zfs"},
]
def test_error_code_returned_on_failure(self):
"""Failures surface the LXD error code for easier debugging."""
FakeLXDClient.responses = {
("GET", "/1.0/storage-pools?recursion=1"): {
"type": "error",
"error": "unavailable",
"error_code": 503,
}
}
with patch.object(self.module, "LXDClient", FakeLXDClient):
with patch.object(self.module.os.path, "exists", return_value=False):
with self.assertRaises(AnsibleFailJson) as exc:
with set_module_args({}):
self.module.main()
result = exc.exception.args[0]
assert result["error_code"] == 503
assert "Failed to retrieve storage pools" in result["msg"]

View file

@ -0,0 +1,156 @@
# Copyright (c) 2025, Sean McAvoy (@smcavoy) <seanmcavoy@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import annotations
from unittest.mock import patch
from ansible_collections.community.general.plugins.modules import lxd_storage_volume_info as module
from ansible_collections.community.internal_test_tools.tests.unit.plugins.modules.utils import (
AnsibleExitJson,
AnsibleFailJson,
ModuleTestCase,
set_module_args,
)
class FakeLXDClient:
responses: dict[tuple[str, str], dict] = {}
def __init__(self, url, key_file=None, cert_file=None, debug=False, **kwargs):
self.url = url
self.key_file = key_file
self.cert_file = cert_file
self.debug = debug
self.logs = [{"type": "fake-request"}] if debug else []
def authenticate(self, trust_password):
self.trust_password = trust_password
def do(self, method, url, ok_error_codes=None, **kwargs):
try:
return self.responses[(method, url)]
except KeyError as exc:
raise AssertionError(f"Unexpected call: {method} {url}") from exc
class TestLXDStorageVolumeInfo(ModuleTestCase):
def setUp(self):
super().setUp()
self.module = module
def test_returns_all_volumes_for_pool(self):
"""Volume metadata is returned for every volume in the pool using recursion."""
FakeLXDClient.responses = {
("GET", "/1.0/storage-pools/default"): {"type": "sync", "metadata": {"name": "default"}},
("GET", "/1.0/storage-pools/default/volumes?recursion=1"): {
"type": "sync",
"metadata": [
{"name": "data", "type": "custom"},
{"name": "web", "type": "container"},
],
},
}
with patch.object(self.module, "LXDClient", FakeLXDClient):
with patch.object(self.module.os.path, "exists", return_value=False):
with self.assertRaises(AnsibleExitJson) as exc:
with set_module_args({"pool": "default"}):
self.module.main()
result = exc.exception.args[0]
assert result["storage_volumes"] == [
{"name": "data", "type": "custom"},
{"name": "web", "type": "container"},
]
def test_returns_specific_storage_volume(self):
"""When name is specified without type, recursion is used to find it."""
FakeLXDClient.responses = {
("GET", "/1.0/storage-pools/default"): {"type": "sync", "metadata": {"name": "default"}},
("GET", "/1.0/storage-pools/default/volumes?recursion=1"): {
"type": "sync",
"metadata": [
{"name": "data", "type": "custom"},
{"name": "web", "type": "container"},
],
},
}
with patch.object(self.module, "LXDClient", FakeLXDClient):
with patch.object(self.module.os.path, "exists", return_value=False):
with self.assertRaises(AnsibleExitJson) as exc:
with set_module_args({"pool": "default", "name": "data"}):
self.module.main()
result = exc.exception.args[0]
assert result["storage_volumes"] == [
{"name": "data", "type": "custom"},
]
def test_filters_storage_volumes_by_type(self):
"""Volumes can be filtered by type using recursion."""
FakeLXDClient.responses = {
("GET", "/1.0/storage-pools/default"): {"type": "sync", "metadata": {"name": "default"}},
("GET", "/1.0/storage-pools/default/volumes?recursion=1"): {
"type": "sync",
"metadata": [
{"name": "data", "type": "custom"},
{"name": "web", "type": "container"},
],
},
}
with patch.object(self.module, "LXDClient", FakeLXDClient):
with patch.object(self.module.os.path, "exists", return_value=False):
with self.assertRaises(AnsibleExitJson) as exc:
with set_module_args({"pool": "default", "type": "container"}):
self.module.main()
result = exc.exception.args[0]
assert result["storage_volumes"] == [
{"name": "web", "type": "container"},
]
def test_returns_specific_volume_with_type_using_direct_request(self):
"""When both name and type are specified, a direct API call is made (no recursion)."""
FakeLXDClient.responses = {
("GET", "/1.0/storage-pools/default"): {"type": "sync", "metadata": {"name": "default"}},
("GET", "/1.0/storage-pools/default/volumes/custom/data"): {
"type": "sync",
"metadata": {"name": "data", "type": "custom", "config": {"size": "10GiB"}},
},
}
with patch.object(self.module, "LXDClient", FakeLXDClient):
with patch.object(self.module.os.path, "exists", return_value=False):
with self.assertRaises(AnsibleExitJson) as exc:
with set_module_args({"pool": "default", "name": "data", "type": "custom"}):
self.module.main()
result = exc.exception.args[0]
assert result["storage_volumes"] == [
{"name": "data", "type": "custom", "config": {"size": "10GiB"}},
]
def test_error_code_returned_when_listing_volumes_fails(self):
"""Errors from LXD are surfaced with the numeric code."""
FakeLXDClient.responses = {
("GET", "/1.0/storage-pools/default"): {"type": "sync", "metadata": {"name": "default"}},
("GET", "/1.0/storage-pools/default/volumes?recursion=1"): {
"type": "error",
"error": "service unavailable",
"error_code": 503,
},
}
with patch.object(self.module, "LXDClient", FakeLXDClient):
with patch.object(self.module.os.path, "exists", return_value=False):
with self.assertRaises(AnsibleFailJson) as exc:
with set_module_args({"pool": "default"}):
self.module.main()
result = exc.exception.args[0]
assert result["error_code"] == 503
assert "Failed to retrieve volumes from pool" in result["msg"]