Merge ~martin-kalcok/juju-verify:target-machine into ~canonical-solutions-engineering/juju-verify:master

Proposed by Martin Kalcok
Status: Merged
Approved by: Alvaro Uria
Approved revision: 7a42c7f1d2045d685fe0d3e12e10cfde29e02b9d
Merged at revision: 12f01da7d8512b654b725c489c131757db214d3f
Proposed branch: ~martin-kalcok/juju-verify:target-machine
Merge into: ~canonical-solutions-engineering/juju-verify:master
Diff against target: 168 lines (+105/-4)
2 files modified
juju_verify/juju_verify.py (+26/-2)
tests/unit/test_juju_verify.py (+79/-2)
Reviewer Review Type Date Requested Status
Alvaro Uria (community) Approve
Canonical Solutions Engineering Team Pending
Canonical Solutions Engineering Team Pending
Review via email: mp+398114@code.launchpad.net

Commit message

Allow targeting juju-verify on machines with -M/--machine option

Description of the change

Added option -M/--machines to juju-verify that allows targeting juju machines instead of Units.

This does not have impact on overall juju-verify functionality, it serves as a shortcut that removes the need to list every principal charm of the machine manually.

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Alvaro Uria (aluria) wrote :

All good. I've added a minor suggestion to use list comprehension instead of .append

Revision history for this message
Alvaro Uria (aluria) wrote :

+1, ty

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision 12f01da7d8512b654b725c489c131757db214d3f

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/juju_verify/juju_verify.py b/juju_verify/juju_verify.py
2index bb105c0..6469229 100644
3--- a/juju_verify/juju_verify.py
4+++ b/juju_verify/juju_verify.py
5@@ -59,6 +59,18 @@ async def find_units(model: Model, units: List[str]) -> List[Unit]:
6 return selected_units
7
8
9+async def find_units_on_machine(model: Model, machines: List[str]) -> List[Unit]:
10+ """Find all principal units that run on selected machines.
11+
12+ :param model: Juju model to search units in
13+ :param machines: names of Juju machines on which to search units
14+ :return: List of juju.Unit objects that match units running on the machines
15+ """
16+ return [unit for _, unit in model.units.items()
17+ if unit.machine.entity_id in machines
18+ and not unit.data.get("subordinate")]
19+
20+
21 async def connect_model(model_name: Union[str, None]) -> Model:
22 """Connect to a custom or default Juju model.
23
24@@ -88,11 +100,15 @@ def parse_args() -> argparse.Namespace:
25 help='Connect to specific model.')
26 parser.add_argument('check', choices=BaseVerifier.supported_checks(),
27 type=str.lower, help='Check to verify.')
28- parser.add_argument('units', nargs='+', help='Units to check.')
29 parser.add_argument('-l', '--log-level', type=str.lower,
30 help='Set amount of displayed information',
31 default='info', choices=['trace', 'debug', 'info'])
32
33+ target = parser.add_mutually_exclusive_group(required=True)
34+ target.add_argument('--units', '-u', nargs='+', type=str,
35+ help='Units to check.')
36+ target.add_argument('--machines', '-M', nargs='+', type=str,
37+ help='Check all units on the machine.')
38 return parser.parse_args()
39
40
41@@ -119,7 +135,15 @@ def main() -> None:
42 args = parse_args()
43 config_logger(args.log_level)
44 model = loop.run(connect_model(args.model))
45- units = loop.run(find_units(model, args.units))
46+ units: List[Unit] = []
47+
48+ if args.units:
49+ units = loop.run(find_units(model, args.units))
50+ elif args.machines:
51+ units = loop.run(find_units_on_machine(model, args.machines))
52+ else:
53+ fail('juju-verify must target either juju units or juju machines')
54+
55 try:
56 verifier = get_verifier(units)
57 result = verifier.verify(args.check)
58diff --git a/tests/unit/test_juju_verify.py b/tests/unit/test_juju_verify.py
59index 5b938aa..6bdbbb2 100644
60--- a/tests/unit/test_juju_verify.py
61+++ b/tests/unit/test_juju_verify.py
62@@ -99,6 +99,32 @@ async def test_find_units(model, all_units, fail):
63 fail.assert_called_once_with(expected_message)
64
65
66+@pytest.mark.asyncio
67+async def test_find_units_on_machine(model, all_units):
68+ """Test that find_units_on_machine function returns correct units."""
69+ machine_1_name = '0'
70+ machine_2_name = '1'
71+
72+ machine_1 = MagicMock()
73+ machine_1.entity_id = machine_1_name
74+ machine_2 = MagicMock()
75+ machine_2.entity_id = machine_2_name
76+
77+ machine_1_units = all_units[:3]
78+ machine_2_units = all_units[3:]
79+
80+ for unit_name, unit in model.units.items():
81+ if unit_name in machine_1_units:
82+ unit.machine = machine_1
83+ elif unit_name in machine_2_units:
84+ unit.machine = machine_2
85+
86+ found_units = await juju_verify.find_units_on_machine(model,
87+ [machine_1_name])
88+
89+ assert machine_1_units == [unit.entity_id for unit in found_units]
90+
91+
92 @pytest.mark.parametrize('log_level, log_constant',
93 [('debug', logging.DEBUG),
94 ('DEBUG', logging.DEBUG),
95@@ -148,13 +174,14 @@ def test_parse_args(mocker):
96 parser.assert_called_once()
97
98
99-def test_main_entrypoint(mocker):
100- """Verify workflow if the main entrypoint."""
101+def test_main_entrypoint_target_units(mocker):
102+ """Verify workflow of the main entrypoint when script targets units."""
103 args = MagicMock()
104 args.log_level = 'info'
105 args.model = None
106 args.check = 'shutdown'
107 args.units = ['nova-compute/0']
108+ args.machines = None
109
110 result = Result(True, 'Passed')
111 verifier = MagicMock()
112@@ -175,6 +202,56 @@ def test_main_entrypoint(mocker):
113 logger.info.assert_called_with(result.format())
114
115
116+def test_main_entrypoint_target_machine(mocker):
117+ """Verify workflow of the main entrypoint when script targets machines."""
118+ args = MagicMock()
119+ args.log_level = 'info'
120+ args.model = None
121+ args.check = 'shutdown'
122+ args.machines = ['0']
123+ args.units = None
124+ expected_units = ['nova-compute/0']
125+
126+ result = Result(True, 'Passed')
127+ verifier = MagicMock()
128+ verifier.verify.return_value = result
129+
130+ mocker.patch.object(juju_verify, 'parse_args').return_value = args
131+ mocker.patch.object(juju_verify, 'get_verifier').return_value = verifier
132+ mocker.patch.object(juju_verify, 'loop')
133+ mocker.patch.object(juju_verify, 'connect_model', new_callable=MagicMock())
134+ mocker.patch.object(juju_verify, 'find_units_on_machine',
135+ new_callable=MagicMock()).return_value = expected_units
136+ logger = mocker.patch.object(juju_verify, 'logger')
137+
138+ juju_verify.main()
139+
140+ juju_verify.connect_model.assert_called_with(args.model)
141+ juju_verify.find_units_on_machine.assert_called_with(ANY, args.machines)
142+ verifier.verify.asssert_called_with(args.check)
143+ logger.info.assert_called_with(result.format())
144+
145+
146+def test_main_entrypoint_no_target_fail(mocker, fail):
147+ """Test that main fails if not target (units/machines) is specified."""
148+ args = MagicMock()
149+ args.log_level = 'info'
150+ args.model = None
151+ args.check = 'shutdown'
152+ args.machines = None
153+ args.units = None
154+
155+ expected_msg = 'juju-verify must target either juju units or juju machines'
156+
157+ mocker.patch.object(juju_verify, 'parse_args').return_value = args
158+ mocker.patch.object(juju_verify, 'connect_model', new_callable=MagicMock())
159+ mocker.patch.object(juju_verify, 'loop')
160+
161+ juju_verify.main()
162+
163+ fail.assert_any_call(expected_msg)
164+
165+
166 @pytest.mark.parametrize('error, error_msg',
167 [(CharmException, 'Charm failure'),
168 (VerificationError, 'Verification Failure'),

Subscribers

People subscribed via source and target branches