cleanup(inventory): Use dicts where possible

Use dicts instead of ResourceFields where possible to allow
easier serialization/deserialization of objects fetched from the K8S
API.

Signed-off-by: Felix Matouschek <fmatouschek@redhat.com>
This commit is contained in:
Felix Matouschek
2024-07-04 16:26:53 +02:00
parent 033f373c94
commit c4570b2089
10 changed files with 221 additions and 167 deletions

View File

@@ -136,7 +136,6 @@ from typing import (
# potentially print a warning to the user if the client is missing. # potentially print a warning to the user if the client is missing.
try: try:
from kubernetes.dynamic.exceptions import DynamicApiError from kubernetes.dynamic.exceptions import DynamicApiError
from kubernetes.dynamic.resource import ResourceField
HAS_K8S_MODULE_HELPER = True HAS_K8S_MODULE_HELPER = True
K8S_IMPORT_EXCEPTION = None K8S_IMPORT_EXCEPTION = None
@@ -147,17 +146,11 @@ except ImportError as e:
Dummy class, mainly used for ansible-test sanity. Dummy class, mainly used for ansible-test sanity.
""" """
class ResourceField:
"""
Dummy class, mainly used for ansible-test sanity.
"""
HAS_K8S_MODULE_HELPER = False HAS_K8S_MODULE_HELPER = False
K8S_IMPORT_EXCEPTION = e K8S_IMPORT_EXCEPTION = e
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
get_api_client, get_api_client,
K8SClient, K8SClient,
@@ -293,6 +286,20 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
""" """
return InventoryModule.snake_case_pattern.sub("_", name).lower() return InventoryModule.snake_case_pattern.sub("_", name).lower()
@staticmethod
def obj_is_valid(obj: Dict) -> bool:
"""
obj_is_valid ensures commonly used keys are present in the passed object.
"""
return bool(
"spec" in obj
and "status" in obj
and "metadata" in obj
and obj["metadata"].get("name")
and obj["metadata"].get("namespace")
and obj["metadata"].get("uid")
)
@staticmethod @staticmethod
def get_host_from_service(service: Dict, node_name: Optional[str]) -> Optional[str]: def get_host_from_service(service: Dict, node_name: Optional[str]) -> Optional[str]:
""" """
@@ -360,9 +367,6 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
return False return False
def __init__(self) -> None:
super().__init__()
def verify_file(self, path: str) -> None: def verify_file(self, path: str) -> None:
""" """
verify_file ensures the inventory file is compatible with this plugin. verify_file ensures the inventory file is compatible with this plugin.
@@ -480,7 +484,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
def get_resources( def get_resources(
self, client: K8SClient, api_version: str, kind: str, **kwargs self, client: K8SClient, api_version: str, kind: str, **kwargs
) -> List[ResourceField]: ) -> List[Dict]:
""" """
get_resources uses a dynamic K8SClient to fetch resources from the K8S API. get_resources uses a dynamic K8SClient to fetch resources from the K8S API.
""" """
@@ -493,7 +497,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
f"Error fetching {kind} list: {self.format_dynamic_api_exc(exc)}" f"Error fetching {kind} list: {self.format_dynamic_api_exc(exc)}"
) from exc ) from exc
return result.items return [item.to_dict() for item in result.items]
def get_available_namespaces(self, client: K8SClient) -> List[str]: def get_available_namespaces(self, client: K8SClient) -> List[str]:
""" """
@@ -501,13 +505,14 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
configured credentials and returns them. configured credentials and returns them.
""" """
return [ return [
namespace.metadata.name namespace["metadata"]["name"]
for namespace in self.get_resources(client, "v1", "Namespace") for namespace in self.get_resources(client, "v1", "Namespace")
if "metadata" in namespace and "name" in namespace["metadata"]
] ]
def get_vms_for_namespace( def get_vms_for_namespace(
self, client: K8SClient, namespace: str, opts: InventoryOptions self, client: K8SClient, namespace: str, opts: InventoryOptions
) -> List[ResourceField]: ) -> List[Dict]:
""" """
get_vms_for_namespace returns a list of all VirtualMachines in a namespace. get_vms_for_namespace returns a list of all VirtualMachines in a namespace.
""" """
@@ -521,7 +526,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
def get_vmis_for_namespace( def get_vmis_for_namespace(
self, client: K8SClient, namespace: str, opts: InventoryOptions self, client: K8SClient, namespace: str, opts: InventoryOptions
) -> List[ResourceField]: ) -> List[Dict]:
""" """
get_vmis_for_namespace returns a list of all VirtualMachineInstances in a namespace. get_vmis_for_namespace returns a list of all VirtualMachineInstances in a namespace.
""" """
@@ -538,7 +543,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
get_ssh_services_for_namespace retrieves all services of a namespace exposing port 22/ssh. get_ssh_services_for_namespace retrieves all services of a namespace exposing port 22/ssh.
The services are mapped to the name of the corresponding domain. The services are mapped to the name of the corresponding domain.
""" """
service_list = self.get_resources( items = self.get_resources(
client, client,
"v1", "v1",
"Service", "Service",
@@ -546,12 +551,12 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
) )
services = {} services = {}
for service in service_list: for service in items:
# Continue if service is not of type LoadBalancer or NodePort # Continue if service is not of type LoadBalancer or NodePort
if service.get("spec") is None: if not (spec := service.get("spec")):
continue continue
if service["spec"].get("type") not in ( if spec.get("type") not in (
TYPE_LOADBALANCER, TYPE_LOADBALANCER,
TYPE_NODEPORT, TYPE_NODEPORT,
): ):
@@ -559,17 +564,20 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
# Continue if ports are not defined, there are more than one port mapping # Continue if ports are not defined, there are more than one port mapping
# or the target port is not port 22/ssh # or the target port is not port 22/ssh
ports = service["spec"].get("ports") if (
if ports is None or len(ports) != 1 or ports[0].get("targetPort") != 22: (ports := spec.get("ports")) is None
or len(ports) != 1
or ports[0].get("targetPort") != 22
):
continue continue
# Only add the service to the dict if the domain selector is present # Only add the service to the dict if the domain selector is present
domain = service["spec"].get("selector", {}).get(LABEL_KUBEVIRT_IO_DOMAIN) if domain := spec.get("selector", {}).get(LABEL_KUBEVIRT_IO_DOMAIN):
if domain is not None:
services[domain] = service services[domain] = service
return services return services
def populate_inventory_from_namespace( def populate_inventory_from_namespace(
self, client: K8SClient, name: str, namespace: str, opts: InventoryOptions self, client: K8SClient, name: str, namespace: str, opts: InventoryOptions
) -> None: ) -> None:
@@ -578,12 +586,14 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
namespace to the inventory. namespace to the inventory.
""" """
vms = { vms = {
vm.metadata.name: vm vm["metadata"]["name"]: vm
for vm in self.get_vms_for_namespace(client, namespace, opts) for vm in self.get_vms_for_namespace(client, namespace, opts)
if self.obj_is_valid(vm)
} }
vmis = { vmis = {
vmi.metadata.name: vmi vmi["metadata"]["name"]: vmi
for vmi in self.get_vmis_for_namespace(client, namespace, opts) for vmi in self.get_vmis_for_namespace(client, namespace, opts)
if self.obj_is_valid(vmi)
} }
if not vms and not vmis: if not vms and not vmis:
@@ -601,7 +611,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
# Add found VMs and optionally enhance with VMI data # Add found VMs and optionally enhance with VMI data
for name, vm in vms.items(): for name, vm in vms.items():
hostname = self.add_host(vm, opts.host_format, namespace_group) hostname = self.add_host(vm["metadata"], opts.host_format, namespace_group)
self.set_vars_from_vm(hostname, vm, opts) self.set_vars_from_vm(hostname, vm, opts)
if name in vmis: if name in vmis:
self.set_vars_from_vmi(hostname, vmis[name], services, opts) self.set_vars_from_vmi(hostname, vmis[name], services, opts)
@@ -611,36 +621,32 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
for name, vmi in vmis.items(): for name, vmi in vmis.items():
if name in vms: if name in vms:
continue continue
hostname = self.add_host(vmi, opts.host_format, namespace_group) hostname = self.add_host(vmi["metadata"], opts.host_format, namespace_group)
self.set_vars_from_vmi(hostname, vmi, services, opts) self.set_vars_from_vmi(hostname, vmi, services, opts)
self.set_composable_vars(hostname) self.set_composable_vars(hostname)
def add_host( def add_host(self, metadata: Dict, host_format: str, namespace_group: str) -> str:
self, obj: ResourceField, host_format: str, namespace_group: str
) -> str:
""" """
add_hosts adds a host to the inventory. add_hosts adds a host to the inventory.
""" """
hostname = host_format.format( hostname = host_format.format(
namespace=obj.metadata.namespace, namespace=metadata["namespace"],
name=obj.metadata.name, name=metadata["name"],
uid=obj.metadata.uid, uid=metadata["uid"],
) )
self.inventory.add_host(hostname) self.inventory.add_host(hostname)
self.inventory.add_child(namespace_group, hostname) self.inventory.add_child(namespace_group, hostname)
return hostname return hostname
def set_vars_from_vm( def set_vars_from_vm(self, hostname: str, vm: Dict, opts: InventoryOptions) -> None:
self, hostname: str, vm: ResourceField, opts: InventoryOptions
) -> None:
""" """
set_vars_from_vm sets inventory variables from a VM prefixed with vm_. set_vars_from_vm sets inventory variables from a VM prefixed with vm_.
""" """
self.set_common_vars(hostname, "vm", vm, opts) self.set_common_vars(hostname, "vm", vm, opts)
def set_vars_from_vmi( def set_vars_from_vmi(
self, hostname: str, vmi: ResourceField, services: Dict, opts: InventoryOptions self, hostname: str, vmi: Dict, services: Dict, opts: InventoryOptions
) -> None: ) -> None:
""" """
set_vars_from_vmi sets inventory variables from a VMI prefixed with vmi_ and set_vars_from_vmi sets inventory variables from a VMI prefixed with vmi_ and
@@ -648,76 +654,76 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
""" """
self.set_common_vars(hostname, "vmi", vmi, opts) self.set_common_vars(hostname, "vmi", vmi, opts)
if not (interfaces := vmi["status"].get("interfaces")):
return
if opts.network_name is None: if opts.network_name is None:
# Use first interface # Use first interface
interface = vmi.status.interfaces[0] if vmi.status.interfaces else None interface = interfaces[0]
else: else:
# Find interface by its name # Find interface by its name
interface = next( interface = next(
(i for i in vmi.status.interfaces if i.name == opts.network_name), (i for i in interfaces if i.get("name") == opts.network_name),
None, None,
) )
# If interface is not found or IP address is not reported skip this VMI # If interface is not found or IP address is not reported skip this VMI
if interface is None or interface.ipAddress is None: if not interface or not interface.get("ipAddress"):
return return
# Set up the connection # Set up the connection
service = None service = None
if self.is_windows( if self.is_windows(
{} if not vmi.status.guestOSInfo else vmi.status.guestOSInfo.to_dict(), vmi["status"].get("guestOSInfo", {}),
{} if not vmi.metadata.annotations else vmi.metadata.annotations.to_dict(), vmi["metadata"].get("annotations", {}),
): ):
self.inventory.set_variable(hostname, "ansible_connection", "winrm") self.inventory.set_variable(hostname, "ansible_connection", "winrm")
else: else:
service = services.get(vmi.metadata.labels.get(LABEL_KUBEVIRT_IO_DOMAIN)) service = services.get(
vmi["metadata"].get("labels", {}).get(LABEL_KUBEVIRT_IO_DOMAIN)
)
self.set_ansible_host_and_port( self.set_ansible_host_and_port(
vmi, vmi,
hostname, hostname,
interface.ipAddress, interface["ipAddress"],
service, service,
opts, opts,
) )
def set_common_vars( def set_common_vars(
self, hostname: str, prefix: str, obj: ResourceField, opts: InventoryOptions self, hostname: str, prefix: str, obj: Dict, opts: InventoryOptions
): ):
""" """
set_common_vars sets common inventory variables from VMs or VMIs. set_common_vars sets common inventory variables from VMs or VMIs.
""" """
# Add hostvars from metadata # Add hostvars from metadata
if metadata := obj.metadata: if annotations := obj["metadata"].get("annotations"):
if metadata.annotations: self.inventory.set_variable(hostname, f"{prefix}_annotations", annotations)
self.inventory.set_variable( if labels := obj["metadata"].get("labels"):
hostname, f"{prefix}_annotations", metadata.annotations.to_dict() self.inventory.set_variable(hostname, f"{prefix}_labels", labels)
) # Create label groups and add vm to it if enabled
if metadata.labels: if opts.create_groups:
self.inventory.set_variable( self.set_groups_from_labels(hostname, labels)
hostname, f"{prefix}_labels", metadata.labels.to_dict() if resource_version := obj["metadata"].get("resourceVersion"):
) self.inventory.set_variable(
# Create label groups and add vm to it if enabled hostname, f"{prefix}_resource_version", resource_version
if opts.create_groups: )
self.set_groups_from_labels(hostname, metadata.labels) if uid := obj["metadata"].get("uid"):
if metadata.resourceVersion: self.inventory.set_variable(hostname, f"{prefix}_uid", uid)
self.inventory.set_variable(
hostname, f"{prefix}_resource_version", metadata.resourceVersion
)
if metadata.uid:
self.inventory.set_variable(hostname, f"{prefix}_uid", metadata.uid)
# Add hostvars from status # Add hostvars from status
if obj.status: for key, value in obj["status"].items():
for key, value in obj.status.to_dict().items(): self.inventory.set_variable(
name = self.format_var_name(key) hostname, f"{prefix}_{self.format_var_name(key)}", value
self.inventory.set_variable(hostname, f"{prefix}_{name}", value) )
def set_groups_from_labels(self, hostname: str, labels: ResourceField) -> None: def set_groups_from_labels(self, hostname: str, labels: Dict) -> None:
""" """
set_groups_from_labels adds groups for each label of a VM or VMI and set_groups_from_labels adds groups for each label of a VM or VMI and
adds the host to each group. adds the host to each group.
""" """
groups = [] groups = []
for key, value in labels.to_dict().items(): for key, value in labels.items():
group_name = self._sanitize_group_name(f"label_{key}_{value}") group_name = self._sanitize_group_name(f"label_{key}_{value}")
if group_name not in groups: if group_name not in groups:
groups.append(group_name) groups.append(group_name)
@@ -728,7 +734,7 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
def set_ansible_host_and_port( def set_ansible_host_and_port(
self, self,
vmi: ResourceField, vmi: Dict,
hostname: str, hostname: str,
ip_address: str, ip_address: str,
service: Optional[Dict], service: Optional[Dict],
@@ -743,15 +749,13 @@ class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
if opts.kube_secondary_dns and opts.network_name: if opts.kube_secondary_dns and opts.network_name:
# Set ansible_host to the kubesecondarydns derived host name if enabled # Set ansible_host to the kubesecondarydns derived host name if enabled
# See https://github.com/kubevirt/kubesecondarydns#parameters # See https://github.com/kubevirt/kubesecondarydns#parameters
ansible_host = ( ansible_host = f"{opts.network_name}.{vmi['metadata']['name']}.{vmi['metadata']['namespace']}.vm"
f"{opts.network_name}.{vmi.metadata.name}.{vmi.metadata.namespace}.vm"
)
if opts.base_domain: if opts.base_domain:
ansible_host += f".{opts.base_domain}" ansible_host += f".{opts.base_domain}"
elif opts.use_service and service and not opts.network_name: elif opts.use_service and service and not opts.network_name:
# Set ansible_host and ansible_port to the host and port from the LoadBalancer # Set ansible_host and ansible_port to the host and port from the LoadBalancer
# or NodePort service exposing SSH # or NodePort service exposing SSH
node_name = vmi.status.nodeName node_name = vmi["status"].get("nodeName")
if node_name and opts.append_base_domain and opts.base_domain: if node_name and opts.append_base_domain and opts.base_domain:
node_name += f".{opts.base_domain}" node_name += f".{opts.base_domain}"
host = self.get_host_from_service(service, node_name) host = self.get_host_from_service(service, node_name)

View File

@@ -22,7 +22,9 @@ BASE_VMI = {
"metadata": { "metadata": {
"name": "testvmi", "name": "testvmi",
"namespace": "default", "namespace": "default",
"uid": "e86c603c-fb13-4933-bf67-de100bdba0c3",
}, },
"spec": {},
"status": { "status": {
"interfaces": [{"ipAddress": "10.10.10.10"}], "interfaces": [{"ipAddress": "10.10.10.10"}],
}, },

View File

@@ -21,7 +21,9 @@ VMI = {
"metadata": { "metadata": {
"name": "testvmi", "name": "testvmi",
"namespace": "default", "namespace": "default",
"uid": "6ffdef43-6c39-4441-a088-82d319ea5c13",
}, },
"spec": {},
"status": { "status": {
"interfaces": [{"ipAddress": "10.10.10.10"}], "interfaces": [{"ipAddress": "10.10.10.10"}],
"migrationMethod": "BlockMigration", "migrationMethod": "BlockMigration",

View File

@@ -24,6 +24,7 @@ VM1 = {
"uid": "940003aa-0160-4b7e-9e55-8ec3df72047f", "uid": "940003aa-0160-4b7e-9e55-8ec3df72047f",
}, },
"spec": {"running": True}, "spec": {"running": True},
"status": {},
} }
VM2 = { VM2 = {
@@ -33,6 +34,7 @@ VM2 = {
"uid": "c2c68de5-b9d7-4c25-872f-462e7245b3e6", "uid": "c2c68de5-b9d7-4c25-872f-462e7245b3e6",
}, },
"spec": {"running": False}, "spec": {"running": False},
"status": {},
} }
VMI1 = { VMI1 = {
@@ -41,6 +43,7 @@ VMI1 = {
"namespace": "default", "namespace": "default",
"uid": "a84319a9-db31-4a36-9b66-3e387578f871", "uid": "a84319a9-db31-4a36-9b66-3e387578f871",
}, },
"spec": {},
"status": { "status": {
"interfaces": [{"ipAddress": "10.10.10.10"}], "interfaces": [{"ipAddress": "10.10.10.10"}],
}, },

View File

@@ -8,8 +8,6 @@ __metaclass__ = type
import pytest import pytest
from addict import Dict
from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import ( from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
InventoryModule, InventoryModule,
) )
@@ -48,6 +46,55 @@ def test_format_var_name(name, expected):
assert InventoryModule.format_var_name(name) == expected assert InventoryModule.format_var_name(name) == expected
@pytest.mark.parametrize(
"obj,expected",
[
({}, False),
({"spec": {}}, False),
({"status": {}}, False),
({"metadata": {}}, False),
({"spec": {}, "status": {}}, False),
({"spec": {}, "metadata": {}}, False),
({"status": {}, "metadata": {}}, False),
({"spec": {}, "status": {}, "metadata": {}}, False),
({"spec": {}, "status": {}, "metadata": {}, "something": {}}, False),
({"spec": {}, "status": {}, "metadata": {"name": "test"}}, False),
({"spec": {}, "status": {}, "metadata": {"namespace": "test"}}, False),
({"spec": {}, "status": {}, "metadata": {"uid": "test"}}, False),
(
{
"spec": {},
"status": {},
"metadata": {"name": "test", "namespace": "test"},
},
False,
),
(
{
"spec": {},
"status": {},
"metadata": {"name": "test", "namespace": "test", "something": "test"},
},
False,
),
(
{"spec": {}, "status": {}, "metadata": {"name": "test", "uid": "test"}},
False,
),
(
{
"spec": {},
"status": {},
"metadata": {"name": "test", "namespace": "test", "uid": "test"},
},
True,
),
],
)
def test_obj_is_valid(obj, expected):
assert InventoryModule.obj_is_valid(obj) == expected
@pytest.mark.parametrize( @pytest.mark.parametrize(
"service,node_name,expected", "service,node_name,expected",
[ [
@@ -223,7 +270,7 @@ def test_get_cluster_domain(inventory, client):
) )
def test_set_groups_from_labels(inventory, groups, labels, expected): def test_set_groups_from_labels(inventory, groups, labels, expected):
hostname = "default-testvm" hostname = "default-testvm"
inventory.set_groups_from_labels(hostname, Dict(labels)) inventory.set_groups_from_labels(hostname, labels)
for group in expected: for group in expected:
assert group in groups assert group in groups
assert hostname in groups[group]["children"] assert hostname in groups[group]["children"]

View File

@@ -8,8 +8,6 @@ __metaclass__ = type
import pytest import pytest
from addict import Dict
from ansible_collections.kubevirt.core.tests.unit.plugins.inventory.constants import ( from ansible_collections.kubevirt.core.tests.unit.plugins.inventory.constants import (
DEFAULT_NAMESPACE, DEFAULT_NAMESPACE,
) )
@@ -65,15 +63,11 @@ def test_add_host(inventory, groups, hosts, host_format, expected):
inventory.inventory.add_group(namespace_group) inventory.inventory.add_group(namespace_group)
inventory.add_host( inventory.add_host(
Dict( {
{ "name": "testvm",
"metadata": { "namespace": DEFAULT_NAMESPACE,
"name": "testvm", "uid": "f8abae7c-d792-4b9b-af95-62d322ae5bc1",
"namespace": DEFAULT_NAMESPACE, },
"uid": "f8abae7c-d792-4b9b-af95-62d322ae5bc1",
}
}
),
host_format, host_format,
namespace_group, namespace_group,
) )

View File

@@ -8,9 +8,6 @@ __metaclass__ = type
import pytest import pytest
from addict import Dict
from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import ( from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
InventoryOptions, InventoryOptions,
) )
@@ -25,6 +22,8 @@ VM1 = {
"namespace": DEFAULT_NAMESPACE, "namespace": DEFAULT_NAMESPACE,
"uid": "940003aa-0160-4b7e-9e55-8ec3df72047f", "uid": "940003aa-0160-4b7e-9e55-8ec3df72047f",
}, },
"spec": {},
"status": {},
} }
VM2 = { VM2 = {
@@ -33,6 +32,8 @@ VM2 = {
"namespace": DEFAULT_NAMESPACE, "namespace": DEFAULT_NAMESPACE,
"uid": "c2c68de5-b9d7-4c25-872f-462e7245b3e6", "uid": "c2c68de5-b9d7-4c25-872f-462e7245b3e6",
}, },
"spec": {},
"status": {},
} }
VMI1 = { VMI1 = {
@@ -41,6 +42,7 @@ VMI1 = {
"namespace": DEFAULT_NAMESPACE, "namespace": DEFAULT_NAMESPACE,
"uid": "a84319a9-db31-4a36-9b66-3e387578f871", "uid": "a84319a9-db31-4a36-9b66-3e387578f871",
}, },
"spec": {},
"status": { "status": {
"interfaces": [{"ipAddress": "10.10.10.10"}], "interfaces": [{"ipAddress": "10.10.10.10"}],
}, },
@@ -52,6 +54,7 @@ VMI2 = {
"namespace": DEFAULT_NAMESPACE, "namespace": DEFAULT_NAMESPACE,
"uid": "fd35700a-9cbe-488b-8f32-7adbe57eadc2", "uid": "fd35700a-9cbe-488b-8f32-7adbe57eadc2",
}, },
"spec": {},
"status": { "status": {
"interfaces": [{"ipAddress": "10.10.10.10"}], "interfaces": [{"ipAddress": "10.10.10.10"}],
}, },
@@ -78,20 +81,20 @@ VMI2 = {
def test_populate_inventory_from_namespace( def test_populate_inventory_from_namespace(
mocker, inventory, groups, vms, vmis, expected mocker, inventory, groups, vms, vmis, expected
): ):
_vms = {vm["metadata"]["name"]: Dict(vm) for vm in vms} _vms = {vm["metadata"]["name"]: vm for vm in vms}
_vmis = {vmi["metadata"]["name"]: Dict(vmi) for vmi in vmis} _vmis = {vmi["metadata"]["name"]: vmi for vmi in vmis}
opts = InventoryOptions() opts = InventoryOptions()
def format_hostname(obj): def format_hostname(obj):
return opts.host_format.format( return opts.host_format.format(
namespace=obj.metadata.namespace, namespace=obj["metadata"]["namespace"],
name=obj.metadata.name, name=obj["metadata"]["name"],
uid=obj.metadata.uid, uid=obj["metadata"]["uid"],
) )
def add_host_call(obj): def add_host_call(obj):
return mocker.call( return mocker.call(
obj, obj["metadata"],
opts.host_format, opts.host_format,
f"namespace_{DEFAULT_NAMESPACE}", f"namespace_{DEFAULT_NAMESPACE}",
) )

View File

@@ -8,8 +8,6 @@ __metaclass__ = type
import pytest import pytest
from addict import Dict
from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import ( from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
InventoryOptions, InventoryOptions,
) )
@@ -29,7 +27,7 @@ def test_use_ip_address_by_default(mocker, inventory, opts):
hostname = "default-testvm" hostname = "default-testvm"
ip_address = "1.1.1.1" ip_address = "1.1.1.1"
inventory.set_ansible_host_and_port(Dict(), hostname, ip_address, None, opts) inventory.set_ansible_host_and_port({}, hostname, ip_address, None, opts)
set_variable.assert_has_calls( set_variable.assert_has_calls(
[ [
@@ -50,12 +48,10 @@ def test_kube_secondary_dns(mocker, inventory, base_domain):
set_variable = mocker.patch.object(inventory.inventory, "set_variable") set_variable = mocker.patch.object(inventory.inventory, "set_variable")
hostname = "default-testvm" hostname = "default-testvm"
vmi = Dict( vmi = {
{ "metadata": {"name": "testvm", "namespace": "default"},
"metadata": {"name": "testvm", "namespace": "default"}, "status": {"interfaces": [{"name": "awesome"}]},
"status": {"interfaces": [{"name": "awesome"}]}, }
}
)
inventory.set_ansible_host_and_port( inventory.set_ansible_host_and_port(
vmi, vmi,
@@ -85,12 +81,10 @@ def test_kube_secondary_dns_precedence_over_service(mocker, inventory):
set_variable = mocker.patch.object(inventory.inventory, "set_variable") set_variable = mocker.patch.object(inventory.inventory, "set_variable")
hostname = "default-testvm" hostname = "default-testvm"
vmi = Dict( vmi = {
{ "metadata": {"name": "testvm", "namespace": "default"},
"metadata": {"name": "testvm", "namespace": "default"}, "status": {"interfaces": [{"name": "awesome"}]},
"status": {"interfaces": [{"name": "awesome"}]}, }
}
)
inventory.set_ansible_host_and_port( inventory.set_ansible_host_and_port(
vmi, vmi,
@@ -170,13 +164,11 @@ def test_service(mocker, inventory, service, expected_host, expected_port):
set_variable = mocker.patch.object(inventory.inventory, "set_variable") set_variable = mocker.patch.object(inventory.inventory, "set_variable")
hostname = "default-testvm" hostname = "default-testvm"
vmi = Dict( vmi = {
{ "status": {
"status": { "nodeName": "testnode.example.com",
"nodeName": "testnode.example.com", },
}, }
}
)
inventory.set_ansible_host_and_port( inventory.set_ansible_host_and_port(
vmi, vmi,
@@ -198,13 +190,11 @@ def test_service_append_base_domain(mocker, inventory):
set_variable = mocker.patch.object(inventory.inventory, "set_variable") set_variable = mocker.patch.object(inventory.inventory, "set_variable")
hostname = "default-testvm" hostname = "default-testvm"
vmi = Dict( vmi = {
{ "status": {
"status": { "nodeName": "testnode",
"nodeName": "testnode", },
}, }
}
)
service = { service = {
"spec": { "spec": {
"type": "NodePort", "type": "NodePort",
@@ -243,13 +233,11 @@ def test_service_fallback(mocker, inventory, host, port):
mocker.patch.object(inventory, "get_port_from_service", return_value=port) mocker.patch.object(inventory, "get_port_from_service", return_value=port)
hostname = "default-testvm" hostname = "default-testvm"
vmi = Dict( vmi = {
{ "status": {
"status": { "nodeName": "testnode",
"nodeName": "testnode", },
}, }
}
)
inventory.set_ansible_host_and_port( inventory.set_ansible_host_and_port(
vmi, vmi,
hostname, hostname,
@@ -271,7 +259,7 @@ def test_no_service_if_network_name(mocker, inventory):
hostname = "default-testvm" hostname = "default-testvm"
inventory.set_ansible_host_and_port( inventory.set_ansible_host_and_port(
Dict(), {},
hostname, hostname,
"1.2.3.4", "1.2.3.4",
{"something": "something"}, {"something": "something"},

View File

@@ -11,8 +11,6 @@ from string import ascii_lowercase
import pytest import pytest
from addict import Dict
from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import ( from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
InventoryOptions, InventoryOptions,
) )
@@ -26,6 +24,8 @@ from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
"metadata": { "metadata": {
"something": "idontcare", "something": "idontcare",
}, },
"spec": {},
"status": {},
}, },
{}, {},
), ),
@@ -34,6 +34,8 @@ from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
"metadata": { "metadata": {
"annotations": {"testanno": "testval"}, "annotations": {"testanno": "testval"},
}, },
"spec": {},
"status": {},
}, },
{"annotations": {"testanno": "testval"}}, {"annotations": {"testanno": "testval"}},
), ),
@@ -42,6 +44,8 @@ from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
"metadata": { "metadata": {
"labels": {"testlabel": "testval"}, "labels": {"testlabel": "testval"},
}, },
"spec": {},
"status": {},
}, },
{"labels": {"testlabel": "testval"}}, {"labels": {"testlabel": "testval"}},
), ),
@@ -50,6 +54,8 @@ from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
"metadata": { "metadata": {
"resourceVersion": "123", "resourceVersion": "123",
}, },
"spec": {},
"status": {},
}, },
{"resource_version": "123"}, {"resource_version": "123"},
), ),
@@ -58,11 +64,15 @@ from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
"metadata": { "metadata": {
"uid": "48e6ed2c-d8a2-4172-844d-0fe7056aa180", "uid": "48e6ed2c-d8a2-4172-844d-0fe7056aa180",
}, },
"spec": {},
"status": {},
}, },
{"uid": "48e6ed2c-d8a2-4172-844d-0fe7056aa180"}, {"uid": "48e6ed2c-d8a2-4172-844d-0fe7056aa180"},
), ),
( (
{ {
"metadata": {},
"spec": {},
"status": { "status": {
"interfaces": [{"ipAddress": "10.10.10.10"}], "interfaces": [{"ipAddress": "10.10.10.10"}],
}, },
@@ -75,7 +85,7 @@ def test_set_common_vars(inventory, hosts, obj, expected):
hostname = "default-testvm" hostname = "default-testvm"
prefix = "".join(choice(ascii_lowercase) for i in range(5)) prefix = "".join(choice(ascii_lowercase) for i in range(5))
inventory.inventory.add_host(hostname) inventory.inventory.add_host(hostname)
inventory.set_common_vars(hostname, prefix, Dict(obj), InventoryOptions()) inventory.set_common_vars(hostname, prefix, obj, InventoryOptions())
for key, value in expected.items(): for key, value in expected.items():
prefixed_key = f"{prefix}_{key}" prefixed_key = f"{prefix}_{key}"
@@ -99,7 +109,7 @@ def test_set_common_vars_create_groups(mocker, inventory, create_groups):
opts = InventoryOptions(create_groups=create_groups) opts = InventoryOptions(create_groups=create_groups)
inventory.set_common_vars( inventory.set_common_vars(
hostname, "prefix", Dict({"metadata": {"labels": labels}}), opts hostname, "prefix", {"metadata": {"labels": labels}, "status": {}}, opts
) )
if create_groups: if create_groups:
@@ -111,12 +121,16 @@ def test_set_common_vars_create_groups(mocker, inventory, create_groups):
def test_called_by_set_vars_from(mocker, inventory): def test_called_by_set_vars_from(mocker, inventory):
hostname = "default-testvm" hostname = "default-testvm"
opts = InventoryOptions() opts = InventoryOptions()
obj = {"status": {}}
set_common_vars = mocker.patch.object(inventory, "set_common_vars") set_common_vars = mocker.patch.object(inventory, "set_common_vars")
inventory.set_vars_from_vm(hostname, Dict(), opts) inventory.set_vars_from_vm(hostname, obj, opts)
inventory.set_vars_from_vmi(hostname, Dict(), {}, opts) inventory.set_vars_from_vmi(hostname, obj, {}, opts)
set_common_vars.assert_has_calls( set_common_vars.assert_has_calls(
[mocker.call(hostname, "vm", {}, opts), mocker.call(hostname, "vmi", {}, opts)] [
mocker.call(hostname, "vm", obj, opts),
mocker.call(hostname, "vmi", obj, opts),
]
) )

View File

@@ -6,8 +6,6 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
from addict import Dict
from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import ( from ansible_collections.kubevirt.core.plugins.inventory.kubevirt import (
InventoryOptions, InventoryOptions,
LABEL_KUBEVIRT_IO_DOMAIN, LABEL_KUBEVIRT_IO_DOMAIN,
@@ -20,7 +18,7 @@ def test_ignore_vmi_without_interface(mocker, inventory):
inventory, "set_ansible_host_and_port" inventory, "set_ansible_host_and_port"
) )
vmi = Dict({"status": {}}) vmi = {"status": {}}
inventory.set_vars_from_vmi("default-testvm", vmi, {}, InventoryOptions()) inventory.set_vars_from_vmi("default-testvm", vmi, {}, InventoryOptions())
set_ansible_host_and_port.assert_not_called() set_ansible_host_and_port.assert_not_called()
@@ -33,9 +31,10 @@ def test_use_first_interface_by_default(mocker, inventory):
) )
hostname = "default-testvm" hostname = "default-testvm"
vmi = Dict( vmi = {
{"status": {"interfaces": [{"ipAddress": "1.1.1.1"}, {"ipAddress": "2.2.2.2"}]}} "metadata": {},
) "status": {"interfaces": [{"ipAddress": "1.1.1.1"}, {"ipAddress": "2.2.2.2"}]},
}
opts = InventoryOptions() opts = InventoryOptions()
inventory.set_vars_from_vmi(hostname, vmi, {}, opts) inventory.set_vars_from_vmi(hostname, vmi, {}, opts)
@@ -51,16 +50,15 @@ def test_use_named_interface(mocker, inventory):
) )
hostname = "default-testvm" hostname = "default-testvm"
vmi = Dict( vmi = {
{ "metadata": {},
"status": { "status": {
"interfaces": [ "interfaces": [
{"name": "first", "ipAddress": "1.1.1.1"}, {"name": "first", "ipAddress": "1.1.1.1"},
{"name": "second", "ipAddress": "2.2.2.2"}, {"name": "second", "ipAddress": "2.2.2.2"},
] ]
} },
} }
)
opts = InventoryOptions(network_name="second") opts = InventoryOptions(network_name="second")
inventory.set_vars_from_vmi(hostname, vmi, {}, opts) inventory.set_vars_from_vmi(hostname, vmi, {}, opts)
@@ -75,9 +73,10 @@ def test_ignore_vmi_without_named_interface(mocker, inventory):
inventory, "set_ansible_host_and_port" inventory, "set_ansible_host_and_port"
) )
vmi = Dict( vmi = {
{"status": {"interfaces": [{"name": "somename", "ipAddress": "1.1.1.1"}]}} "metadata": {},
) "status": {"interfaces": [{"name": "somename", "ipAddress": "1.1.1.1"}]},
}
inventory.set_vars_from_vmi( inventory.set_vars_from_vmi(
"default-testvm", vmi, {}, InventoryOptions(network_name="awesome") "default-testvm", vmi, {}, InventoryOptions(network_name="awesome")
) )
@@ -92,7 +91,7 @@ def test_set_winrm_if_windows(mocker, inventory):
set_variable = mocker.patch.object(inventory.inventory, "set_variable") set_variable = mocker.patch.object(inventory.inventory, "set_variable")
hostname = "default-testvm" hostname = "default-testvm"
vmi = Dict({"status": {"interfaces": [{"ipAddress": "1.1.1.1"}]}}) vmi = {"metadata": {}, "status": {"interfaces": [{"ipAddress": "1.1.1.1"}]}}
inventory.set_vars_from_vmi(hostname, vmi, {}, InventoryOptions()) inventory.set_vars_from_vmi(hostname, vmi, {}, InventoryOptions())
set_variable.assert_called_once_with(hostname, "ansible_connection", "winrm") set_variable.assert_called_once_with(hostname, "ansible_connection", "winrm")
@@ -105,12 +104,10 @@ def test_service_lookup(mocker, inventory):
) )
hostname = "default-testvm" hostname = "default-testvm"
vmi = Dict( vmi = {
{ "metadata": {"labels": {LABEL_KUBEVIRT_IO_DOMAIN: "testdomain"}},
"metadata": {"labels": {LABEL_KUBEVIRT_IO_DOMAIN: "testdomain"}}, "status": {"interfaces": [{"name": "somename", "ipAddress": "1.1.1.1"}]},
"status": {"interfaces": [{"name": "somename", "ipAddress": "1.1.1.1"}]}, }
}
)
opts = InventoryOptions() opts = InventoryOptions()
service = {"metadata": {"name": "testsvc"}} service = {"metadata": {"name": "testsvc"}}
inventory.set_vars_from_vmi(hostname, vmi, {"testdomain": service}, opts) inventory.set_vars_from_vmi(hostname, vmi, {"testdomain": service}, opts)