cleanup(inventory): Drop support for multiple connections

The support for connections to multiple clusters in the inventory
plugin is dropped to better align with user expectations and how other
inventories work. If inventories of multiple clusters are needed the
inventory can be run multiple times with different configurations.
This also helps to clean up the code and make it simpler.

For now this adds a compatibility helper so that configurations with a
single connection entry remain supported and a warning is emitted.

Signed-off-by: Felix Matouschek <fmatouschek@redhat.com>
This commit is contained in:
Felix Matouschek
2024-07-03 11:45:21 +02:00
parent 2e9d6cec15
commit fd9c30103d
12 changed files with 394 additions and 394 deletions

View File

@@ -1,7 +1,6 @@
---
plugin: kubevirt.core.kubevirt
connections:
- name: test
namespaces:
- default
create_groups: true
name: test
namespaces:
- default
create_groups: true

View File

@@ -1,7 +1,6 @@
---
plugin: kubevirt.core.kubevirt
connections:
- namespaces:
- default
create_groups: true
label_selector: app=test
namespaces:
- default
create_groups: true
label_selector: app=test

View File

@@ -1,7 +1,6 @@
---
plugin: kubevirt.core.kubevirt
connections:
- namespaces:
- default
create_groups: true
network_name: bridge-network
namespaces:
- default
create_groups: true
network_name: bridge-network

View File

@@ -1,6 +1,5 @@
---
plugin: kubevirt.core.kubevirt
connections:
- namespaces:
- default
network_name: bridge-network
namespaces:
- default
network_name: bridge-network

View File

@@ -206,29 +206,6 @@ def test_is_windows(guest_os_info, annotations, expected):
assert InventoryModule.is_windows(guest_os_info, annotations) == expected
def test_parse(mocker, inventory):
path = "/testpath"
cache_prefix = "test-prefix"
host_format = "test-format"
config_data = {"host_format": host_format}
cache = True
get_cache_prefix = mocker.patch.object(
inventory, "_get_cache_prefix", return_value=cache_prefix
)
read_config_data = mocker.patch.object(
inventory, "_read_config_data", return_value=config_data
)
setup = mocker.patch.object(inventory, "setup")
inventory.parse(None, None, path, cache)
get_cache_prefix.assert_called_once_with(path)
read_config_data.assert_called_once_with(path)
setup.assert_called_once_with(config_data, cache, cache_prefix)
assert inventory.host_format == host_format
def test_get_cluster_domain(inventory, client):
assert inventory.get_cluster_domain(client) == DEFAULT_BASE_DOMAIN

View File

@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
# Copyright 2024 Red Hat, Inc.
# Apache License 2.0 (see LICENSE or http://www.apache.org/licenses/LICENSE-2.0)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import pytest
from ansible.errors import AnsibleError
from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
KubeVirtInventoryException,
)
def test_config_data_without_connections_ignored(inventory):
config_data = {
"name": "connection-1",
"namespaces": ["default"],
"network_name": "bridge-network",
"label_selector": "app=test",
}
inventory.connections_compatibility(config_data)
assert config_data["name"] == "connection-1"
assert config_data["namespaces"] == ["default"]
assert config_data["network_name"] == "bridge-network"
assert config_data["label_selector"] == "app=test"
def test_single_connection_supported(inventory):
config_data = {
"connections": [
{
"name": "connection-1",
"namespaces": ["default"],
"network_name": "bridge-network",
"label_selector": "app=test",
}
],
"namespaces": ["something"],
"network_name": "some-network",
"label_selector": "app=something",
}
inventory.connections_compatibility(config_data)
assert config_data["name"] == "connection-1"
assert config_data["namespaces"] == ["default"]
assert config_data["network_name"] == "bridge-network"
assert config_data["label_selector"] == "app=test"
def test_multiple_connections_not_supported(inventory):
with pytest.raises(
AnsibleError, match="Split your connections into multiple configuration files."
):
inventory.connections_compatibility(
{
"connections": [
{
"name": "connection-1",
},
{
"name": "connection-2",
},
],
}
)
@pytest.mark.parametrize(
"config_data,expected",
[
({"connections": "test"}, "Expecting connections to be a list."),
(
{"connections": [["test", "test"]]},
"Expecting connection to be a dictionary.",
),
],
)
def test_connections_exceptions(inventory, config_data, expected):
with pytest.raises(KubeVirtInventoryException, match=expected):
inventory.connections_compatibility(config_data)

View File

@@ -11,7 +11,6 @@ import pytest
from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
InventoryOptions,
KubeVirtInventoryException,
)
from ansible_collections.kubevirt.core.tests.unit.plugins.inventory.constants import (
@@ -22,135 +21,97 @@ from ansible_collections.kubevirt.core.plugins.inventory import kubevirt
@pytest.mark.parametrize(
"connections,expected",
"config_data,expected",
[
(
None,
[
{
"name": "default-hostname",
"namespace": DEFAULT_NAMESPACE,
"opts": InventoryOptions(),
},
],
{
"name": "default-hostname",
"namespaces": [DEFAULT_NAMESPACE],
"opts": InventoryOptions(),
},
),
(
[
{
"name": "test",
},
],
[
{
"name": "test",
"namespace": DEFAULT_NAMESPACE,
"opts": InventoryOptions(),
},
],
{
"name": "test",
},
{
"name": "test",
"namespaces": [DEFAULT_NAMESPACE],
"opts": InventoryOptions(),
},
),
(
[
{"name": "test", "namespaces": ["test"]},
],
[
{"name": "test", "namespace": "test", "opts": InventoryOptions()},
],
{"name": "test", "namespaces": ["test"]},
{"name": "test", "namespaces": ["test"], "opts": InventoryOptions()},
),
(
[
{
"name": "test",
"namespaces": ["test"],
"use_service": True,
"create_groups": True,
"append_base_domain": True,
"base_domain": "test-domain",
},
],
[
{
"name": "test",
"namespace": "test",
"opts": InventoryOptions(
use_service=True,
create_groups=True,
append_base_domain=True,
base_domain="test-domain",
),
},
],
{
"name": "test",
"namespaces": ["test"],
"use_service": True,
"create_groups": True,
"append_base_domain": True,
"base_domain": "test-domain",
},
{
"name": "test",
"namespaces": ["test"],
"opts": InventoryOptions(
use_service=True,
create_groups=True,
append_base_domain=True,
base_domain="test-domain",
),
},
),
(
[
{
"name": "test",
"namespaces": ["test"],
"use_service": True,
"create_groups": True,
"append_base_domain": True,
"base_domain": "test-domain",
"network_name": "test-network",
},
],
[
{
"name": "test",
"namespace": "test",
"opts": InventoryOptions(
use_service=True,
create_groups=True,
append_base_domain=True,
base_domain="test-domain",
network_name="test-network",
),
},
],
{
"name": "test",
"namespaces": ["test"],
"use_service": True,
"create_groups": True,
"append_base_domain": True,
"base_domain": "test-domain",
"network_name": "test-network",
},
{
"name": "test",
"namespaces": ["test"],
"opts": InventoryOptions(
use_service=True,
create_groups=True,
append_base_domain=True,
base_domain="test-domain",
network_name="test-network",
),
},
),
(
[
{
"name": "test",
"namespaces": ["test"],
"use_service": True,
"create_groups": True,
"append_base_domain": True,
"base_domain": "test-domain",
"interface_name": "test-interface",
},
],
[
{
"name": "test",
"namespace": "test",
"opts": InventoryOptions(
use_service=True,
create_groups=True,
append_base_domain=True,
base_domain="test-domain",
network_name="test-interface",
),
},
],
),
(
[
{
"name": "test",
},
{"name": "test", "namespaces": ["test"]},
],
[
{
"name": "test",
"namespace": DEFAULT_NAMESPACE,
"opts": InventoryOptions(),
},
{"name": "test", "namespace": "test", "opts": InventoryOptions()},
],
{
"name": "test",
"namespaces": ["test"],
"use_service": True,
"create_groups": True,
"append_base_domain": True,
"base_domain": "test-domain",
"interface_name": "test-interface",
},
{
"name": "test",
"namespaces": ["test"],
"opts": InventoryOptions(
use_service=True,
create_groups=True,
append_base_domain=True,
base_domain="test-domain",
network_name="test-interface",
),
},
),
],
)
def test_fetch_objects(mocker, inventory, connections, expected):
def test_fetch_objects(mocker, inventory, config_data, expected):
mocker.patch.object(kubevirt, "get_api_client")
mocker.patch.object(
inventory, "get_default_host_name", return_value="default-hostname"
@@ -158,8 +119,7 @@ def test_fetch_objects(mocker, inventory, connections, expected):
cluster_domain = "test.com"
mocker.patch.object(inventory, "get_cluster_domain", return_value=cluster_domain)
for e in expected:
e["opts"].base_domain = e["opts"].base_domain or cluster_domain
expected["opts"].base_domain = expected["opts"].base_domain or cluster_domain
get_available_namespaces = mocker.patch.object(
inventory, "get_available_namespaces", return_value=[DEFAULT_NAMESPACE]
@@ -168,26 +128,16 @@ def test_fetch_objects(mocker, inventory, connections, expected):
inventory, "populate_inventory_from_namespace"
)
inventory.fetch_objects(connections)
inventory.fetch_objects(config_data)
if config_data and "namespaces" in config_data:
get_available_namespaces.assert_not_called()
else:
get_available_namespaces.assert_called()
get_available_namespaces.assert_has_calls(
[mocker.call(mocker.ANY) for c in connections or [{}] if "namespaces" not in c]
)
populate_inventory_from_namespace.assert_has_calls(
[
mocker.call(mocker.ANY, e["name"], e["namespace"], e["opts"])
for e in expected
mocker.call(mocker.ANY, expected["name"], namespace, expected["opts"])
for namespace in expected["namespaces"]
]
)
@pytest.mark.parametrize(
"connections,expected",
[
("test", "Expecting connections to be a list."),
(["test", "test"], "Expecting connection to be a dictionary."),
],
)
def test_fetch_objects_exceptions(inventory, connections, expected):
with pytest.raises(KubeVirtInventoryException, match=expected):
inventory.fetch_objects(connections)

View File

@@ -24,20 +24,28 @@ from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
False,
],
)
def test_cache_is_used(mocker, inventory, cache):
connections = [{"test-conn": {}}]
config_data = {"connections": connections}
cache_key = "test-prefix"
def test_parse(mocker, inventory, cache):
path = "/testpath"
cache_prefix = "test-prefix"
config_data = {"host_format": "test-format"}
mocker.patch.dict(inventory._cache, {cache_key: {"test-key": "test-value"}})
mocker.patch.dict(inventory._cache, {cache_prefix: {"test-key": "test-value"}})
get_cache_prefix = mocker.patch.object(
inventory, "_get_cache_prefix", return_value=cache_prefix
)
read_config_data = mocker.patch.object(
inventory, "_read_config_data", return_value=config_data
)
fetch_objects = mocker.patch.object(inventory, "fetch_objects")
inventory.setup(config_data, cache, cache_key)
inventory.parse(None, None, path, cache)
get_cache_prefix.assert_called_once_with(path)
read_config_data.assert_called_once_with(path)
if cache:
fetch_objects.assert_not_called()
else:
fetch_objects.assert_called_once_with(connections)
fetch_objects.assert_called_once_with(config_data)
@pytest.mark.parametrize(
@@ -49,15 +57,17 @@ def test_cache_is_used(mocker, inventory, cache):
)
def test_k8s_client_missing(mocker, inventory, present):
mocker.patch.object(kubevirt, "HAS_K8S_MODULE_HELPER", present)
mocker.patch.object(inventory, "_get_cache_prefix")
mocker.patch.object(inventory, "_read_config_data")
fetch_objects = mocker.patch.object(inventory, "fetch_objects")
if present:
inventory.setup({}, False, "test")
inventory.parse(None, None, "", False)
fetch_objects.assert_called_once()
else:
with pytest.raises(
KubeVirtInventoryException,
match="This module requires the Kubernetes Python client. Try `pip install kubernetes`. Detail: None",
):
inventory.setup({}, False, "test")
inventory.parse(None, None, "", False)
fetch_objects.assert_not_called()