Skip to content

Commit 065d3de

Browse files
committed
chore: remplement best guess for idle timer
1 parent 03e7d51 commit 065d3de

6 files changed

Lines changed: 51 additions & 51 deletions

File tree

juju/model/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3298,7 +3298,8 @@ async def new_wait_for_idle(
32983298
if any(not isinstance(o, str) for o in apps):
32993299
raise TypeError(f"apps must be a Iterable[str], got {apps=}")
33003300

3301-
deadline = None if timeout is None else time.monotonic() + timeout
3301+
started = time.monotonic()
3302+
deadline = None if timeout is None else started + timeout
33023303

33033304
async def status_on_demand():
33043305
yield _idle.check(
@@ -3316,6 +3317,7 @@ async def status_on_demand():
33163317
wait_for_units=wait_for_units,
33173318
idle_period=idle_period,
33183319
):
3320+
logger.info(f"wait_for_idle start{time.monotonic() - started:+.1f} {done=}")
33193321
if done:
33203322
break
33213323

juju/model/_idle.py

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,59 +44,58 @@ async def loop(
4444
idle_since: dict[str, float] = {}
4545

4646
async for status in foo:
47-
logger.warning("FIXME unit test debug %r", status)
47+
logger.info("wait_for_idle iteration %s", status)
4848
now = time.monotonic()
4949

5050
if not status:
5151
yield False
5252
continue
5353

5454
expected_idle_since = now - idle_period
55-
rv = True
5655

5756
# FIXME there's some confusion about what a "busy" unit is
5857
# are we ready when over the last idle_period, every time sampled:
5958
# a. >=N units were ready (possibly different each time), or
6059
# b. >=N units were ready each time
6160
for name in status.units:
62-
if name in status.ready_units:
61+
if name in status.idle_units:
6362
idle_since[name] = min(now, idle_since.get(name, float("inf")))
6463
else:
6564
idle_since[name] = float("inf")
6665

66+
if busy := {n for n, t in idle_since.items() if t > expected_idle_since}:
67+
logger.info("Waiting for units to be idle enough: %s", busy)
68+
yield False
69+
continue
70+
6771
for app_name in apps:
6872
ready_units = [
6973
n for n in status.ready_units if n.startswith(f"{app_name}/")
7074
]
7175
if len(ready_units) < wait_for_units:
72-
logger.warn(
76+
logger.info(
7377
"Waiting for app %r units %s >= %s",
7478
app_name,
7579
len(status.ready_units),
7680
wait_for_units,
7781
)
78-
rv = False
82+
yield False
83+
continue
7984

8085
if (
8186
wait_for_exact_units is not None
8287
and len(ready_units) != wait_for_exact_units
8388
):
84-
logger.warn(
89+
logger.info(
8590
"Waiting for app %r units %s == %s",
8691
app_name,
8792
len(ready_units),
8893
wait_for_exact_units,
8994
)
90-
rv = False
91-
92-
# FIXME possible interaction between "wait_for_units" and "idle_period"
93-
# Assume that we've got some units ready and some busy
94-
# What are the semantics for returning True?
95-
if busy := [n for n, t in idle_since.items() if t > expected_idle_since]:
96-
logger.warn("Waiting for %s to be idle enough", busy)
97-
rv = False
95+
yield False
96+
continue
9897

99-
yield rv
98+
yield True
10099

101100

102101
def check(
@@ -175,24 +174,19 @@ def check(
175174
rv = CheckStatus(set(), set(), set())
176175

177176
for app_name in apps:
178-
ready_units = []
179177
app = full_status.applications[app_name]
180178
assert isinstance(app, ApplicationStatus)
181179
for unit_name, unit in _app_units(full_status, app_name).items():
182180
rv.units.add(unit_name)
183181
assert unit.agent_status
184182
assert unit.workload_status
185183

186-
if unit.agent_status.status != "idle":
187-
continue
188-
if status and unit.workload_status.status != status:
189-
continue
184+
if unit.agent_status.status == "idle":
185+
rv.idle_units.add(unit_name)
190186

191-
ready_units.append(unit)
192-
rv.ready_units.add(unit_name)
187+
if not status or unit.workload_status.status == status:
188+
rv.ready_units.add(unit_name)
193189

194-
# FIXME
195-
# rv.idle_units -- depends on agent status only, not workload status
196190
return rv
197191

198192

juju/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66

77
DEFAULT_ARCHITECTURE = "amd64"
88

9-
CLIENT_VERSION = "3.6.1.0rc2"
9+
CLIENT_VERSION = "3.6.1.0rc3"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "juju"
3-
version = "3.6.1.0rc2" # Stop-gap until dynamic versioning is done; must be in sync with juju/version.py:CLIENT_VERSION
3+
version = "3.6.1.0rc3" # Stop-gap until dynamic versioning is done; must be in sync with juju/version.py:CLIENT_VERSION
44
description = "Python library for Juju"
55
readme = "docs/readme.rst"
66
license = { file = "LICENSE" }

tests/unit/test_idle_check.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import copy
66
import json
77
from typing import Any
8+
from unittest.mock import ANY
89

910
import pytest
1011

@@ -31,7 +32,12 @@ def test_check_status(full_status: FullStatus, kwargs):
3132
"mysql-test-app/0",
3233
"mysql-test-app/1",
3334
},
34-
set(),
35+
{
36+
"grafana-agent-k8s/0",
37+
"hexanator/0",
38+
"mysql-test-app/0",
39+
"mysql-test-app/1",
40+
},
3541
)
3642

3743

@@ -44,7 +50,7 @@ def test_check_status_missing_app(full_status: FullStatus, kwargs):
4450
def test_check_status_is_selective(full_status: FullStatus, kwargs):
4551
kwargs["apps"] = ["hexanator"]
4652
status = check(full_status, **kwargs)
47-
assert status == CheckStatus({"hexanator/0"}, {"hexanator/0"}, set())
53+
assert status == CheckStatus({"hexanator/0"}, {"hexanator/0"}, {"hexanator/0"})
4854

4955

5056
def test_no_apps(full_status: FullStatus, kwargs):
@@ -80,33 +86,21 @@ def test_app_error(response: dict[str, Any], kwargs):
8086
assert "big problem" in str(e)
8187

8288

83-
def test_exact_count(response: dict[str, Any], kwargs):
84-
units = response["response"]["applications"]["hexanator"]["units"]
85-
units["hexanator/1"] = units["hexanator/0"]
86-
87-
kwargs["apps"] = ["hexanator"]
88-
89-
status = check(convert(response), **kwargs)
90-
assert status == CheckStatus(
91-
{"hexanator/0", "hexanator/1"}, {"hexanator/0", "hexanator/1"}, set()
92-
)
93-
94-
9589
def test_ready_units(full_status: FullStatus, kwargs):
9690
kwargs["apps"] = ["mysql-test-app"]
9791
status = check(full_status, **kwargs)
9892
assert status == CheckStatus(
9993
{"mysql-test-app/0", "mysql-test-app/1"},
10094
{"mysql-test-app/0", "mysql-test-app/1"},
101-
set(),
95+
{"mysql-test-app/0", "mysql-test-app/1"},
10296
)
10397

10498

10599
def test_active_units(full_status: FullStatus, kwargs):
106100
kwargs["apps"] = ["mysql-test-app"]
107101
kwargs["status"] = "active"
108102
status = check(full_status, **kwargs)
109-
assert status == CheckStatus({"mysql-test-app/0", "mysql-test-app/1"}, set(), set())
103+
assert status == CheckStatus({"mysql-test-app/0", "mysql-test-app/1"}, set(), ANY)
110104

111105

112106
def test_ready_unit_requires_idle_agent(response: dict[str, Any], kwargs):
@@ -118,9 +112,11 @@ def test_ready_unit_requires_idle_agent(response: dict[str, Any], kwargs):
118112
kwargs["status"] = "active"
119113

120114
status = check(convert(response), **kwargs)
121-
assert status
122-
assert status.units == {"hexanator/0", "hexanator/1"}
123-
assert status.ready_units == {"hexanator/0"}
115+
assert status == CheckStatus(
116+
{"hexanator/0", "hexanator/1"},
117+
{"hexanator/0", "hexanator/1"},
118+
{"hexanator/0"},
119+
)
124120

125121

126122
def test_ready_unit_requires_workload_status(response: dict[str, Any], kwargs):
@@ -132,7 +128,7 @@ def test_ready_unit_requires_workload_status(response: dict[str, Any], kwargs):
132128
kwargs["status"] = "active"
133129

134130
status = check(convert(response), **kwargs)
135-
assert status == CheckStatus({"hexanator/0", "hexanator/1"}, {"hexanator/0"}, set())
131+
assert status == CheckStatus({"hexanator/0", "hexanator/1"}, {"hexanator/0"}, ANY)
136132

137133

138134
def test_agent_error(response: dict[str, Any], kwargs):
@@ -182,7 +178,7 @@ def test_machine_ok(response: dict[str, Any], kwargs):
182178
kwargs["raise_on_error"] = True
183179

184180
status = check(convert(response), **kwargs)
185-
assert status == CheckStatus({"hexanator/0"}, {"hexanator/0"}, set())
181+
assert status == CheckStatus({"hexanator/0"}, {"hexanator/0"}, ANY)
186182

187183

188184
def test_machine_error(response: dict[str, Any], kwargs):
@@ -244,7 +240,7 @@ def test_maintenance(response: dict[str, Any], kwargs):
244240
kwargs["status"] = "maintenance"
245241

246242
status = check(convert(response), **kwargs)
247-
assert status == CheckStatus({"hexanator/0"}, {"hexanator/0"}, set())
243+
assert status == CheckStatus({"hexanator/0"}, {"hexanator/0"}, ANY)
248244

249245

250246
@pytest.fixture

tests/unit/test_idle_check_subordinate.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
def test_subordinate_apps(response: dict[str, Any], kwargs):
1818
status = check(convert(response), **kwargs)
19-
assert status == CheckStatus({"ntp/0", "ubuntu/0"}, {"ntp/0", "ubuntu/0"}, set())
19+
assert status == CheckStatus(
20+
{"ntp/0", "ubuntu/0"},
21+
{"ntp/0", "ubuntu/0"},
22+
{"ntp/0", "ubuntu/0"},
23+
)
2024

2125

2226
def test_subordinate_is_selective(response, kwargs):
@@ -25,7 +29,11 @@ def test_subordinate_is_selective(response, kwargs):
2529
]
2630
subordinates["some-other/0"] = subordinates["ntp/0"]
2731
status = check(convert(response), **kwargs)
28-
assert status == CheckStatus({"ntp/0", "ubuntu/0"}, {"ntp/0", "ubuntu/0"}, set())
32+
assert status == CheckStatus(
33+
{"ntp/0", "ubuntu/0"},
34+
{"ntp/0", "ubuntu/0"},
35+
{"ntp/0", "ubuntu/0"},
36+
)
2937

3038

3139
@pytest.fixture

0 commit comments

Comments
 (0)