Skip to content

Commit 6a7667d

Browse files
committed
juju_utils: fix assumptions about 'subordinate-to'
Few places in the code made assumption that for subordinate charms, the 'subordinate-to' list would contain only principal charms. However that is not the case. If a suboridnate charm is related to other subordinate charms, they show up in each other's 'subordinate-to' lists. This was especially problematic in the 'get_machines_for_application' function which would end up in an infinite loop. Signed-off-by: Martin Kalcok <martin.kalcok@canonical.com>
1 parent 1655161 commit 6a7667d

File tree

4 files changed

+77
-9
lines changed

4 files changed

+77
-9
lines changed

unit_tests/test_zaza_model.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,25 +245,52 @@ def fail_on_use():
245245
self.machine_data = {self.key: self.key_data}
246246
self.unit = "app/1"
247247
self.application = "app"
248+
248249
self.subordinate_application = "subordinate_application"
249250
self.subordinate_application_data = {
250251
"subordinate-to": [self.application],
251252
"units": None}
252253
self.subordinate_unit = "subordinate_application/1"
253254
self.subordinate_unit_data = {
254255
"workload-status": {"status": "active"}}
256+
257+
# Second subordinate app is related both to the principal application
258+
# and to the first subordinate application
259+
self.second_subordinate_application = "second_subordinate_application"
260+
self.second_subordinate_application_data = {
261+
"subordinate-to": [
262+
self.subordinate_application,
263+
self.application
264+
],
265+
"units": None}
266+
self.second_subordinate_unit = "second_subordinate_application/1"
267+
self.second_subordinate_unit_data = {
268+
"workload-status": {"status": "active"}}
269+
# Update the suboridnate list on the first subordinate as well
270+
self.subordinate_application_data["subordinate-to"].insert(
271+
0,
272+
self.second_subordinate_application,
273+
)
274+
255275
self.unit_data = {
256276
"workload-status": {"status": "active"},
257277
"machine": self.machine,
258278
"subordinates": {
259-
self.subordinate_unit: self.subordinate_unit_data}}
279+
self.subordinate_unit: self.subordinate_unit_data,
280+
self.second_subordinate_unit:
281+
self.second_subordinate_unit_data,
282+
}
283+
}
260284
self.application_data = {"units": {
261285
self.unit1.name: self.subordinate_unit_data,
262286
self.unit: self.unit_data}}
263287
self.juju_status = mock.MagicMock()
264288
self.juju_status.applications = {
265289
self.application: self.application_data,
266-
self.subordinate_application: self.subordinate_application_data}
290+
self.subordinate_application: self.subordinate_application_data,
291+
self.second_subordinate_application:
292+
self.second_subordinate_application_data,
293+
}
267294
self.juju_status.machines = self.machine_data
268295

269296
async def _connect_model(model_name):

unit_tests/utilities/test_zaza_utilities_juju.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,18 +78,39 @@ def fail_on_use():
7878
self.unit2_mock.data = {'machine-id': self.machine2}
7979

8080
self.application = "app"
81+
8182
self.subordinate_application = "subordinate_application"
8283
self.subordinate_application_unit = "subordinate_application/0"
8384
self.subordinate_application_data = {
8485
"subordinate-to": [self.application]}
86+
87+
# Second subordinate app is related both to the principal application
88+
# and to the first subordinate application
89+
self.second_subordinate_application = "second_subordinate"
90+
self.second_subordinate_application_unit = "second_subordinate/0"
91+
self.second_subordinate_application_data = {
92+
"subordinate-to": [self.subordinate_application, self.application]}
93+
# Update the suboridnate list on the first subordinate as well
94+
self.subordinate_application_data['subordinate-to'].insert(
95+
0,
96+
self.second_subordinate_application
97+
)
98+
8599
self.application_data = {
86100
"units": {self.unit1: self.unit1_data},
87-
"subordinates": {self.subordinate_application_unit: {}}}
101+
"subordinates": {
102+
self.subordinate_application_unit: {},
103+
self.second_subordinate_application_unit: {},
104+
}
105+
}
88106
self.juju_status = mock.MagicMock()
89107
self.juju_status.name = "juju_status_object"
90108
self.juju_status.applications = {
91109
self.application: self.application_data,
92-
self.subordinate_application: self.subordinate_application_data}
110+
self.subordinate_application: self.subordinate_application_data,
111+
self.second_subordinate_application:
112+
self.second_subordinate_application_data,
113+
}
93114
self.juju_status.machines = {
94115
self.machine0: self.machine0_mock,
95116
self.machine1: self.machine1_mock,

zaza/model.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
from zaza import sync_wrapper
4242
import zaza.utilities.generic as generic_utils
43+
import zaza.utilities.juju as juju_utils
4344
import zaza.utilities.exceptions as zaza_exceptions
4445
from zaza.utilities import deprecate
4546

@@ -2570,8 +2571,14 @@ async def _unit_status():
25702571
# unit
25712572
lead_app_name = subordinate_principal
25722573
if not subordinate_principal:
2573-
lead_app_name = model_status.applications[app][
2574-
'subordinate-to'][0]
2574+
for app_ in model_status.applications[app]['subordinate-to']:
2575+
app_status = model_status.applications[app_]
2576+
if not juju_utils.is_subordinate_application(app_,
2577+
app_status,
2578+
model_name):
2579+
lead_app_name = app_
2580+
break
2581+
25752582
units = model_status.applications[lead_app_name]['units']
25762583
for unit in units.values():
25772584
try:
@@ -2582,7 +2589,8 @@ async def _unit_status():
25822589
pass
25832590
else: # pragma: no cover
25842591
raise ValueError('{} does not exist as a subordinate under a '
2585-
'principal'.format(unit_name))
2592+
'principal to {}'.format(unit_name,
2593+
lead_app_name))
25862594
if negate_match:
25872595
return v != status
25882596
else:

zaza/utilities/juju.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,12 @@ def get_principle_applications(application_name, application_status=None,
122122
status = application_status or get_application_status(
123123
application_name,
124124
model_name=model_name)
125-
return status.get("subordinate-to")
125+
# A subodrinate application can be related to other
126+
# subordinate applications, such that they both show up in
127+
# each other's 'subordinate-to' list. We need to filter them out
128+
# from the list that we are returning.
129+
return [app for app in status.get("subordinate-to")
130+
if not is_subordinate_application(app, model_name=model_name)]
126131

127132

128133
def get_machines_for_application(application, model_name=None):
@@ -142,8 +147,15 @@ def get_machines_for_application(application, model_name=None):
142147
# libjuju juju status no longer has units for subordinate charms
143148
# Use the application it is subordinate-to to find machines
144149
if is_subordinate_application(application, model_name=model_name):
150+
principal = get_principle_applications(application,
151+
model_name=model_name)
152+
if not principal:
153+
logging.warn(f"Suboridnate application {application} is not"
154+
" related to any principal application.")
155+
return
156+
145157
yield from get_machines_for_application(
146-
status.get("subordinate-to")[0],
158+
principal[0],
147159
model_name=model_name)
148160
else:
149161
for unit in status.get("units").keys():

0 commit comments

Comments
 (0)