Skip to content

Commit eba8dec

Browse files
authored
[JUJU-3583] wait_for_idle to not block when enough units are ready (#840)
* Fix wait_for_units flag to not block when enough units ready wait_for_idle will keep waiting if there are less number of units available than requested (via the wait_for_units flag). However, if there are already a number of units in a desired status ready to go, more than (or equal to) wait_for_units, then it shouldn't block until other not-yet-available units to get into that desired state as well. fixes #837 * Add integration test for wait_for_units in wait_for_idle
1 parent 4a96306 commit eba8dec

2 files changed

Lines changed: 40 additions & 2 deletions

File tree

juju/model.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2457,6 +2457,7 @@ async def wait_for_idle(self, apps=None, raise_on_error=True, raise_on_blocked=F
24572457
start_time = datetime.now()
24582458
apps = apps or self.applications
24592459
idle_times = {}
2460+
units_ready = set() # The units that are in the desired state
24602461
last_log_time = None
24612462
log_interval = timedelta(seconds=30)
24622463

@@ -2478,6 +2479,9 @@ def _raise_for_status(entities, status):
24782479
))
24792480

24802481
while True:
2482+
# The list 'busy' is what keeps this loop going,
2483+
# i.e. it'll stop when busy is empty after all the
2484+
# units are scanned
24812485
busy = []
24822486
errors = {}
24832487
blocks = {}
@@ -2490,15 +2494,20 @@ def _raise_for_status(entities, status):
24902494
errors.setdefault("App", []).append(app.name)
24912495
if raise_on_blocked and app.status == "blocked":
24922496
blocks.setdefault("App", []).append(app.name)
2497+
# Check if wait_for_exact_units flag is used
24932498
if wait_for_exact_units > 0:
24942499
if len(app.units) != wait_for_exact_units:
24952500
busy.append(app.name + " (waiting for exactly %s units, current : %s)" %
24962501
(wait_for_exact_units, len(app.units)))
24972502
continue
2503+
# If we have less # of units then required, then wait a bit more
24982504
elif len(app.units) < wait_for_units:
24992505
busy.append(app.name + " (not enough units yet - %s/%s)" %
25002506
(len(app.units), wait_for_units))
25012507
continue
2508+
elif len(units_ready) >= wait_for_units:
2509+
# No need to keep looking, we have the desired number of units ready to go
2510+
break
25022511
for unit in app.units:
25032512
if unit.machine is not None and unit.machine.status == "error":
25042513
errors.setdefault("Machine", []).append(unit.machine.id)
@@ -2512,10 +2521,17 @@ def _raise_for_status(entities, status):
25122521
if raise_on_blocked and unit.workload_status == "blocked":
25132522
blocks.setdefault("Unit", []).append(unit.name)
25142523
continue
2515-
waiting_for_status = status and unit.workload_status != status
2516-
if not waiting_for_status and unit.agent_status == "idle":
2524+
waiting_for_a_particular_status = status and unit.workload_status != status
2525+
if not waiting_for_a_particular_status and unit.agent_status == "idle":
2526+
# We'll be here in two cases:
2527+
# 1) We're not waiting for a particular status and the agent is "idle"
2528+
# 2) We're waiting for a particular status and the workload is in that status
2529+
# Either way, the unit is ready, start measuring the time period that
2530+
# it needs to stay in that state (i.e. idle_period)
2531+
units_ready.add(unit.name)
25172532
now = datetime.now()
25182533
idle_start = idle_times.setdefault(unit.name, now)
2534+
print(f'unit {unit.name} is waiting since : {idle_start} -- now : {now} -- waiting for : {now - idle_start}')
25192535
if now - idle_start < idle_period:
25202536
busy.append("{} [{}] {}: {}".format(unit.name,
25212537
unit.agent_status,

tests/integration/test_model.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,28 @@ async def test_wait_for_idle_with_not_enough_units(event_loop):
774774
await model.wait_for_idle(timeout=5 * 60, wait_for_units=3)
775775

776776

777+
@base.bootstrapped
778+
@pytest.mark.asyncio
779+
async def test_wait_for_idle_more_units_than_needed(event_loop):
780+
async with base.CleanModel() as model:
781+
charm_path = TESTS_DIR / 'charm'
782+
783+
# we add 2 units of a local charm that does nothing
784+
# (i.e. can't go into active/idle)
785+
await model.deploy(str(charm_path), num_units=2)
786+
787+
# then add 1 unit of ubuntu charm
788+
await model.deploy(
789+
'ubuntu',
790+
application_name='ubuntu',
791+
num_units=1,
792+
)
793+
794+
# because the wait_for_units=1, wait_for_idle should return without timing out
795+
# even though there are two more units that aren't active/idle
796+
await model.wait_for_idle(timeout=5 * 60, wait_for_units=1, status='active')
797+
798+
777799
@base.bootstrapped
778800
@pytest.mark.asyncio
779801
async def test_wait_for_idle_with_enough_units(event_loop):

0 commit comments

Comments
 (0)