Skip to content

Commit

Permalink
reimplement unset status back fill
Browse files Browse the repository at this point in the history
  • Loading branch information
dimaqq committed Oct 10, 2024
1 parent 9340f12 commit 1267efb
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 14 deletions.
1 change: 1 addition & 0 deletions juju/client/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ class Connection:

MAX_FRAME_SIZE = 2**22
"Maximum size for a single frame. Defaults to 4MB."
monitor: Monitor

@classmethod
async def connect(
Expand Down
58 changes: 48 additions & 10 deletions juju/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
Optional,
overload,
Set,
Tuple,
TypeVar,
TYPE_CHECKING,
# Union,
Expand All @@ -38,6 +39,7 @@
import yaml
import websockets

import juju.status
from . import provisioner, tag, utils, jasyncio
from .annotationhelper import _get_annotations, _set_annotations
from .bundle import BundleHandler, get_charm_series, is_local_charm
Expand Down Expand Up @@ -929,7 +931,9 @@ def add_local_charm(self, charm_file, series="", size=None):
instead.
"""
conn, headers, path_prefix = self.connection().https_connection()
connection = self.connection()
assert connection
conn, headers, path_prefix = connection.https_connection()
path = "%s/charms?series=%s" % (path_prefix, series)
headers['Content-Type'] = 'application/zip'
if size:
Expand Down Expand Up @@ -1313,12 +1317,14 @@ async def _all_watcher():
del allwatcher.Id
continue
except websockets.ConnectionClosed:
monitor = self.connection().monitor
connection = self.connection()
assert connection
monitor = connection.monitor
if monitor.status == monitor.ERROR:
# closed unexpectedly, try to reopen
log.warning(
'Watcher: connection closed, reopening')
await self.connection().reconnect()
await connection.reconnect()
if monitor.status != monitor.CONNECTED:
# reconnect failed; abort and shutdown
log.error('Watcher: automatic reconnect '
Expand Down Expand Up @@ -3062,13 +3068,11 @@ async def _check_idle(
logging.info("Waiting for app %r", app_name)
return False

reveal_type(app)
reveal_type(app.status)
assert app.status
if app.status.status == "error" and raise_on_error:
raise JujuAppError(f"App {app_name!r} is in error: {app.status.info!r}")
if app.status.status == "blocked" and raise_on_blocked:
raise JujuAppError(f"App {app_name!r} is blocked: {app.status.info!r}")
app_status, app_info = self._compound_status(full_status, app_name)
if app_status == "error" and raise_on_error:
raise JujuAppError(f"App {app_name!r} is in error: {app_info!r}")
if app_status == "blocked" and raise_on_blocked:
raise JujuAppError(f"App {app_name!r} is blocked: {app_info!r}")

# FIXME the old code treats app status "unset" as special
# and uses max(unit statuses) instead (see Application.status)
Expand Down Expand Up @@ -3107,6 +3111,40 @@ async def _check_idle(

return True

def _compound_status(self, full_status: FullStatus, app_name: str) -> Tuple[str, str]:
app = full_status.applications[app_name]
assert app
assert app.status
assert isinstance(app.status.status, str)
assert isinstance(app.status.info, str)
app_status = app.status.status
app_info = app.status.info
unit_details: List[Tuple[str, str]] = []

for unit_name, unit in app.units.items():
assert unit
assert unit.agent_status
assert isinstance(unit.agent_status.status, str)
assert isinstance(unit.agent_status.info, str)
unit_details.append((
unit.agent_status.status,
f"({unit_name}) {unit.agent_status.info}",
))

assert unit.workload_status
assert isinstance(unit.workload_status.status, str)
assert isinstance(unit.workload_status.info, str)
unit_details.append((
unit.workload_status.status,
f"({unit_name}) {unit.workload_status.info}",
))

unit_details.sort(key=lambda t: -juju.status.severity_map.get(t[0], 0))
if app_status == "unset" and unit_details:
return unit_details[0]
else:
return app_status, app_info

async def _check_idle_unit(
self,
unit_name: str,
Expand Down
6 changes: 3 additions & 3 deletions juju/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
def derive_status(statues):
current = 'unknown'
for status in statues:
if status in serverities and serverities[status] > serverities[current]:
if status in severity_map and severity_map[status] > severity_map[current]:
current = status
return current


""" serverities holds status values with a severity measure.
""" severity_map holds status values with a severity measure.
Status values with higher severity are used in preference to others.
"""
serverities = {
severity_map = {
'error': 100,
'blocked': 90,
'waiting': 80,
Expand Down
36 changes: 35 additions & 1 deletion tests/unit/test_wait_for_idle.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@

from juju.application import Application
from juju.client.facade import _convert_response
from juju.client._definitions import FullStatus
from juju.client._definitions import (
ApplicationStatus,
DetailedStatus,
FullStatus,
UnitStatus,
)
from juju.errors import JujuAppError
from juju.machine import Machine
from juju.model import Model
Expand Down Expand Up @@ -106,6 +111,35 @@ async def test_after_idle_waiting_for_unit(full_status_response: dict, kwargs: D
assert not idle and not legacy


def test_compound_status_no_units():
fs = FullStatus()
fs.applications["test"] = app = ApplicationStatus()
app.status = DetailedStatus(status="unset", info="who knows?")
assert app.units is not None
assert not app.units
assert ModelFake()._compound_status(fs, "test") == ("unset", "who knows?")


def test_compound_status_unset():
fs = FullStatus()
fs.applications["test"] = app = ApplicationStatus()
app.status = DetailedStatus(status="unset", info="who knows?")
app.units["test/0"] = unit = UnitStatus()
unit.agent_status = DetailedStatus(status="waiting", info="w")
unit.workload_status = DetailedStatus(status="blocked", info="b")
assert ModelFake()._compound_status(fs, "test") == ("blocked", "(test/0) b")


def test_compound_status_unknown():
fs = FullStatus()
fs.applications["test"] = app = ApplicationStatus()
app.status = DetailedStatus(status="unknown", info="who knows?")
app.units["test/0"] = unit = UnitStatus()
unit.agent_status = DetailedStatus(status="waiting", info="w")
unit.workload_status = DetailedStatus(status="blocked", info="b")
assert ModelFake()._compound_status(fs, "test") == ("unknown", "who knows?")


@pytest.fixture
def kwargs() -> Dict[str, Any]:
return dict(
Expand Down

0 comments on commit 1267efb

Please sign in to comment.