inventory: Add warning for invalid priority values (#86114)

* Handle ValueError raised when user set invalid priority values
* Update tests to work with Pytest

Signed-off-by: Abhijeet Kasurde <Akasurde@redhat.com>
Co-authored-by: Mannu Silva <wise.tent4987@fastmail.com>
pull/85174/merge
Abhijeet Kasurde 4 weeks ago committed by GitHub
parent f743dfce93
commit 3c3a06b8fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,2 @@
minor_changes:
- group - Add warning message when invalid priority values are provided to Group.set_priority() method (https://github.com/ansible/ansible/pull/85468).

@ -1,19 +1,6 @@
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> # (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
# # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations from __future__ import annotations
import typing as t import typing as t
@ -227,7 +214,7 @@ class Group:
Display().deprecated(msg=f'Accepting inventory variable with invalid name {key!r}.', version='2.23', help_text=ex._help_text, obj=ex.obj) Display().deprecated(msg=f'Accepting inventory variable with invalid name {key!r}.', version='2.23', help_text=ex._help_text, obj=ex.obj)
if key == 'ansible_group_priority': if key == 'ansible_group_priority':
self.set_priority(int(value)) self.set_priority(value)
else: else:
if key in self.vars and isinstance(self.vars[key], MutableMapping) and isinstance(value, Mapping): if key in self.vars and isinstance(self.vars[key], MutableMapping) and isinstance(value, Mapping):
self.vars = combine_vars(self.vars, {key: value}) self.vars = combine_vars(self.vars, {key: value})
@ -266,6 +253,10 @@ class Group:
def set_priority(self, priority: int | str) -> None: def set_priority(self, priority: int | str) -> None:
try: try:
self.priority = int(priority) self.priority = int(priority)
except TypeError: except (TypeError, ValueError) as e:
# FIXME: warn about invalid priority display.error_as_warning(
pass msg=f"Invalid priority value '{priority}' for group '{self.name}'."
"Setting priority to default value.",
exception=e,
)
# Keep the existing priority value when conversion fails

@ -1,154 +1,171 @@
# Copyright 2018 Alan Rominger <arominge@redhat.com> # Copyright 2018 Alan Rominger <arominge@redhat.com>
# # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations from __future__ import annotations
import unittest import pytest
from ansible.inventory.group import Group from ansible.inventory.group import Group
from ansible.inventory.host import Host from ansible.inventory.host import Host
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
class TestGroup(unittest.TestCase): def test_depth_update():
A = Group('A')
def test_depth_update(self): B = Group('B')
A = Group('A') Z = Group('Z')
B = Group('B') A.add_child_group(B)
Z = Group('Z') A.add_child_group(Z)
A.add_child_group(B) assert A.depth == 0
A.add_child_group(Z) assert Z.depth == 1
self.assertEqual(A.depth, 0) assert B.depth == 1
self.assertEqual(Z.depth, 1)
self.assertEqual(B.depth, 1)
def test_depth_update_dual_branches():
def test_depth_update_dual_branches(self): alpha = Group('alpha')
alpha = Group('alpha') A = Group('A')
A = Group('A') alpha.add_child_group(A)
alpha.add_child_group(A) B = Group('B')
B = Group('B') A.add_child_group(B)
A.add_child_group(B) Z = Group('Z')
Z = Group('Z') alpha.add_child_group(Z)
alpha.add_child_group(Z) beta = Group('beta')
beta = Group('beta') B.add_child_group(beta)
B.add_child_group(beta) Z.add_child_group(beta)
Z.add_child_group(beta)
assert alpha.depth == 0 # apex
self.assertEqual(alpha.depth, 0) # apex assert beta.depth == 3 # alpha -> A -> B -> beta
self.assertEqual(beta.depth, 3) # alpha -> A -> B -> beta
omega = Group('omega')
omega = Group('omega') omega.add_child_group(alpha)
omega.add_child_group(alpha)
# verify that both paths are traversed to get the max depth value
# verify that both paths are traversed to get the max depth value assert B.depth == 3 # omega -> alpha -> A -> B
self.assertEqual(B.depth, 3) # omega -> alpha -> A -> B assert beta.depth == 4 # B -> beta
self.assertEqual(beta.depth, 4) # B -> beta
def test_depth_recursion(self): def test_depth_recursion():
A = Group('A') A = Group('A')
B = Group('B') B = Group('B')
A.add_child_group(B) A.add_child_group(B)
# hypothetical of adding B as child group to A # hypothetical of adding B as child group to A
A.parent_groups.append(B) A.parent_groups.append(B)
B.child_groups.append(A) B.child_groups.append(A)
# can't update depths of groups, because of loop # can't update depths of groups, because of loop
with self.assertRaises(AnsibleError): with pytest.raises(AnsibleError):
B._check_children_depth() B._check_children_depth()
def test_loop_detection(self):
A = Group('A') def test_loop_detection():
B = Group('B') A = Group('A')
C = Group('C') B = Group('B')
A.add_child_group(B) C = Group('C')
B.add_child_group(C) A.add_child_group(B)
with self.assertRaises(AnsibleError): B.add_child_group(C)
C.add_child_group(A) with pytest.raises(AnsibleError):
C.add_child_group(A)
def test_direct_host_ordering(self):
"""Hosts are returned in order they are added
""" def test_direct_host_ordering():
group = Group('A') """Hosts are returned in order they are added
# host names not added in alphabetical order """
host_name_list = ['z', 'b', 'c', 'a', 'p', 'q'] group = Group('A')
expected_hosts = [] # host names not added in alphabetical order
for host_name in host_name_list: host_name_list = ['z', 'b', 'c', 'a', 'p', 'q']
h = Host(host_name) expected_hosts = []
group.add_host(h) for host_name in host_name_list:
expected_hosts.append(h) h = Host(host_name)
assert group.get_hosts() == expected_hosts group.add_host(h)
expected_hosts.append(h)
def test_sub_group_host_ordering(self): assert group.get_hosts() == expected_hosts
"""With multiple nested groups, asserts that hosts are returned
in deterministic order
""" def test_sub_group_host_ordering():
top_group = Group('A') """With multiple nested groups, asserts that hosts are returned
expected_hosts = [] in deterministic order
for name in ['z', 'b', 'c', 'a', 'p', 'q']: """
child = Group('group_{0}'.format(name)) top_group = Group('A')
top_group.add_child_group(child) expected_hosts = []
host = Host('host_{0}'.format(name)) for name in ['z', 'b', 'c', 'a', 'p', 'q']:
child.add_host(host) child = Group('group_{0}'.format(name))
expected_hosts.append(host) top_group.add_child_group(child)
assert top_group.get_hosts() == expected_hosts host = Host('host_{0}'.format(name))
child.add_host(host)
def test_populates_descendant_hosts(self): expected_hosts.append(host)
A = Group('A') assert top_group.get_hosts() == expected_hosts
B = Group('B')
C = Group('C')
h = Host('h') def test_populates_descendant_hosts():
C.add_host(h) A = Group('A')
A.add_child_group(B) # B is child of A B = Group('B')
B.add_child_group(C) # C is descendant of A C = Group('C')
A.add_child_group(B) h = Host('h')
self.assertEqual(set(h.groups), set([C, B, A])) C.add_host(h)
h2 = Host('h2') A.add_child_group(B) # B is child of A
C.add_host(h2) B.add_child_group(C) # C is descendant of A
self.assertEqual(set(h2.groups), set([C, B, A])) A.add_child_group(B)
assert set(h.groups) == set([C, B, A])
def test_ancestor_example(self): h2 = Host('h2')
# see docstring for Group._walk_relationship C.add_host(h2)
groups = {} assert set(h2.groups) == set([C, B, A])
for name in ['A', 'B', 'C', 'D', 'E', 'F']:
groups[name] = Group(name)
# first row def test_ancestor_example():
groups['A'].add_child_group(groups['D']) # see docstring for Group._walk_relationship
groups['B'].add_child_group(groups['D']) groups = {}
groups['B'].add_child_group(groups['E']) for name in ['A', 'B', 'C', 'D', 'E', 'F']:
groups['C'].add_child_group(groups['D']) groups[name] = Group(name)
# second row # first row
groups['D'].add_child_group(groups['E']) groups['A'].add_child_group(groups['D'])
groups['D'].add_child_group(groups['F']) groups['B'].add_child_group(groups['D'])
groups['E'].add_child_group(groups['F']) groups['B'].add_child_group(groups['E'])
groups['C'].add_child_group(groups['D'])
self.assertEqual( # second row
set(groups['F'].get_ancestors()), groups['D'].add_child_group(groups['E'])
set([ groups['D'].add_child_group(groups['F'])
groups['A'], groups['B'], groups['C'], groups['D'], groups['E'] groups['E'].add_child_group(groups['F'])
])
) assert (
set(groups['F'].get_ancestors()) ==
def test_ancestors_recursive_loop_safe(self): set([
""" groups['A'], groups['B'], groups['C'], groups['D'], groups['E']
The get_ancestors method may be referenced before circular parenting ])
checks, so the method is expected to be stable even with loops )
"""
A = Group('A')
B = Group('B') def test_ancestors_recursive_loop_safe():
A.parent_groups.append(B) """
B.parent_groups.append(A) The get_ancestors method may be referenced before circular parenting
# finishes in finite time checks, so the method is expected to be stable even with loops
self.assertEqual(A.get_ancestors(), set([A, B])) """
A = Group('A')
B = Group('B')
A.parent_groups.append(B)
B.parent_groups.append(A)
# finishes in finite time
assert A.get_ancestors() == set([A, B])
@pytest.mark.parametrize("priority, expected", [
pytest.param(5, 5, id="int"),
pytest.param('10', 10, id="string"),
pytest.param(-1, -1, id="negative number"),
])
def test_set_priority_valid_values(priority, expected):
"""Test that valid priority value is set"""
group = Group('test_group')
group.set_priority(priority)
assert group.priority == expected
@pytest.mark.parametrize("priority, expected", [
pytest.param('invalid', 1, id="invalid string"),
pytest.param(None, 1, id="None"),
pytest.param({'key': 'value'}, 1, id="dict"),
pytest.param(['item'], 1, id="list")
])
def test_set_priority_invalid_values(priority, expected):
"""Test that invalid priority value is handled gracefully with warnings"""
group = Group('test_group')
group.set_priority(priority)
assert group.priority == expected

Loading…
Cancel
Save