Skip to content

Commit

Permalink
chore: wip / squash me
Browse files Browse the repository at this point in the history
  • Loading branch information
dimaqq committed Oct 2, 2024
1 parent 6bde04d commit d73db1d
Showing 1 changed file with 152 additions and 128 deletions.
280 changes: 152 additions & 128 deletions juju/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2878,7 +2878,7 @@ async def wait_for_idle(self,
check_freq=0.5,
status=None,
wait_for_at_least_units=None,
wait_for_exact_units=Union[None, int, Dict[str, int]],
wait_for_exact_units: Union[None, int, Dict[str, int]] = None,
):
"""Wait for applications in the model to settle into an idle state.
Expand Down Expand Up @@ -2944,150 +2944,174 @@ async def wait_for_idle(self,
apps = apps or list(self.applications)
idle_times: Dict[str, datetime] = {}
units_ready: Set[str] = set() # The units that are in the desired state
last_log_time = None
log_interval = timedelta(seconds=30)
last_log_time: List[Optional[datetime]] = [None]

if wait_for_exact_units is not None:
assert isinstance(wait_for_exact_units, int) and wait_for_exact_units >= 0, \
'Invalid value for wait_for_exact_units : %s' % wait_for_exact_units

while True:
# The list 'busy' is what keeps this loop going,
# i.e. it'll stop when busy is empty after all the
# units are scanned
busy: List[str] = []
errors: Dict[Literal["Machine", "Agent", "App", "Unit"], List[Any]] = {}
blocks: Dict[Literal["Machine", "Agent", "App", "Unit"], List[Any]] = {}

# FIXME call FullStatus without filters,
# restrict the result to "expected apps" if that's set
for app_name in apps:
if app_name not in self.applications:
busy.append(app_name + " (missing)")
continue
# TODO
app = self.applications[app_name]
# FIXME: Funny that Application.get_status()
# calls ClientFacade.FullStatus() without patterns
# (that is for all applications and units)
#
# FIXME FullStatus already contains app status
# app.get_status():
# - derive_status(
# - app.status AND
# - app.safe_data["status"]["current"] OR
# - app.units[0..N].workload_status
# - FullStatus().applications.get(app.name)
app_status = await app.get_status()
if raise_on_error and app_status == "error":
# FIXME app_name
errors.setdefault("App", []).append(app.name)
if raise_on_blocked and app_status == "blocked":
# FIXME app_name
blocks.setdefault("App", []).append(app.name)

# Check if wait_for_exact_units flag is used
if wait_for_exact_units is not None:
# FIXME units are listed in FullStatus
if len(app.units) != wait_for_exact_units:
# FIXME app_name
busy.append(app.name + " (waiting for exactly %s units, current : %s)" %
# FIXME units in FullStatus
(wait_for_exact_units, len(app.units)))
continue
# If we have less # of units then required, then wait a bit more
# FIXME units in FullStatus
elif len(app.units) < _wait_for_units:
if (await self._wait_for_idle_cycle(
apps=apps,
raise_on_error=raise_on_error,
raise_on_blocked=raise_on_blocked,
idle_times=idle_times,
units_ready=units_ready,
last_log_time=last_log_time,
start_time=start_time,
)):
return

await jasyncio.sleep(check_freq)

async def _wait_for_idle_cycle(
self,
*,
apps: List[str],
raise_on_error: bool,
raise_on_blocked: bool,
idle_times: Dict[str, datetime] = {},
units_ready: Set[str] = set(), # The units that are in the desired state
last_log_time: List[Optional[datetime]] = [None],
start_time: datetime = datetime.now(),
):
log_interval = timedelta(seconds=30)
# The list 'busy' is what keeps this loop going,
# i.e. it'll stop when busy is empty after all the
# units are scanned
busy: List[str] = []
errors: Dict[Literal["Machine", "Agent", "App", "Unit"], List[Any]] = {}
blocks: Dict[Literal["Machine", "Agent", "App", "Unit"], List[Any]] = {}

# FIXME call FullStatus without filters,
# restrict the result to "expected apps" if that's set
for app_name in apps:
if app_name not in self.applications:
busy.append(app_name + " (missing)")
return False
# TODO
app = self.applications[app_name]
# FIXME: Funny that Application.get_status()
# calls ClientFacade.FullStatus() without patterns
# (that is for all applications and units)
#
# FIXME FullStatus already contains app status
# app.get_status():
# - derive_status(
# - app.status AND
# - app.safe_data["status"]["current"] OR
# - app.units[0..N].workload_status
# - FullStatus().applications.get(app.name)
app_status = await app.get_status()
if raise_on_error and app_status == "error":
# FIXME app_name
errors.setdefault("App", []).append(app.name)
if raise_on_blocked and app_status == "blocked":
# FIXME app_name
blocks.setdefault("App", []).append(app.name)

# Check if wait_for_exact_units flag is used
if wait_for_exact_units is not None:
# FIXME units are listed in FullStatus
if len(app.units) != wait_for_exact_units:
# FIXME app_name
busy.append(app.name + " (not enough units yet - %s/%s)" %
busy.append(app.name + " (waiting for exactly %s units, current : %s)" %
# FIXME units in FullStatus
(len(app.units), _wait_for_units))
continue
# User is waiting for at least a certain # of units, and we have enough
elif wait_for_at_least_units and len(units_ready) >= _wait_for_units:
# So no need to keep looking, we have the desired number of units ready to go,
# exit the loop. Don't just return here, though, we might still have some
# errors to raise at the end
break
# FIXME units in FullStatus
for unit in app.units:
# FIXME ...
if raise_on_error and unit.machine is not None and unit.machine.status == "error":
# FIXME ...
errors.setdefault("Machine", []).append(unit.machine.id)
continue
# FIXME ...
if raise_on_error and unit.agent_status == "error":
# FIXME name is the units key
errors.setdefault("Agent", []).append(unit.name)
continue
# FIXME ...
if raise_on_error and unit.workload_status == "error":
# FIXME name is the units key
errors.setdefault("Unit", []).append(unit.name)
continue
(wait_for_exact_units, len(app.units)))
return False
# If we have less # of units then required, then wait a bit more
# FIXME units in FullStatus
elif len(app.units) < _wait_for_units:
# FIXME app_name
busy.append(app.name + " (not enough units yet - %s/%s)" %
# FIXME units in FullStatus
(len(app.units), _wait_for_units))
return False
# User is waiting for at least a certain # of units, and we have enough
elif wait_for_at_least_units and len(units_ready) >= _wait_for_units:
# So no need to keep looking, we have the desired number of units ready to go,
# exit the loop. Don't just return here, though, we might still have some
# errors to raise at the end
return True
# FIXME units in FullStatus
for unit in app.units:
# FIXME ...
if raise_on_error and unit.machine is not None and unit.machine.status == "error":
# FIXME ...
if raise_on_blocked and unit.workload_status == "blocked":
# FIXME name is the units key
blocks.setdefault("Unit", []).append(unit.name)
continue
# TODO (cderici): we need two versions of wait_for_idle, one for waiting on
# individual units, another one for waiting for an application.
# The convoluted logic below is the result of trying to do both at the same
# time
errors.setdefault("Machine", []).append(unit.machine.id)
return False
# FIXME ...
if raise_on_error and unit.agent_status == "error":
# FIXME name is the units key
errors.setdefault("Agent", []).append(unit.name)
return False
# FIXME ...
if raise_on_error and unit.workload_status == "error":
# FIXME name is the units key
errors.setdefault("Unit", []).append(unit.name)
return False
# FIXME ...
if raise_on_blocked and unit.workload_status == "blocked":
# FIXME name is the units key
blocks.setdefault("Unit", []).append(unit.name)
return False
# TODO (cderici): we need two versions of wait_for_idle, one for waiting on
# individual units, another one for waiting for an application.
# The convoluted logic below is the result of trying to do both at the same
# time
#
# FIXME ...
need_to_wait_more_for_a_particular_status = status and (unit.workload_status != status)
app_is_in_desired_status = (not status) or (app_status == status)
# FIXME ...
if not need_to_wait_more_for_a_particular_status and \
unit.agent_status == "idle" and \
(wait_for_at_least_units or app_is_in_desired_status):
# A unit is ready if either:
# 1) Don't need to wait more for a particular status and the agent is "idle"
# 2) We're looking for a particular status and the unit's workload,
# as well as the application, is in that status. If the user wants to
# see only a particular number of units in that state -- i.e. a subset of
# the units is needed, then we don't care about the application status
# (because e.g. app can be in 'waiting' while unit.0 is 'active' and unit.1
# is 'waiting')

# Either way, the unit is ready, start measuring the time period that
# it needs to stay in that state (i.e. idle_period)
#
# FIXME ...
need_to_wait_more_for_a_particular_status = status and (unit.workload_status != status)
app_is_in_desired_status = (not status) or (app_status == status)
# FIXME ...
if not need_to_wait_more_for_a_particular_status and \
unit.agent_status == "idle" and \
(wait_for_at_least_units or app_is_in_desired_status):
# A unit is ready if either:
# 1) Don't need to wait more for a particular status and the agent is "idle"
# 2) We're looking for a particular status and the unit's workload,
# as well as the application, is in that status. If the user wants to
# see only a particular number of units in that state -- i.e. a subset of
# the units is needed, then we don't care about the application status
# (because e.g. app can be in 'waiting' while unit.0 is 'active' and unit.1
# is 'waiting')

# Either way, the unit is ready, start measuring the time period that
# it needs to stay in that state (i.e. idle_period)
#
# FIXME key in units dict
units_ready.add(unit.name)
now = datetime.now()
# FIXME key in units dict
idle_start = idle_times.setdefault(unit.name, now)

if now - idle_start < idle_period:
busy.append("{} [{}] {}: {}".format(unit.name,
unit.agent_status,
unit.workload_status,
unit.workload_status_message))
else:
# FIXME key in dict
idle_times.pop(unit.name, None)
# FIXME bits in FullStatus
# FIXME key in units dict
units_ready.add(unit.name)
now = datetime.now()
# FIXME key in units dict
idle_start = idle_times.setdefault(unit.name, now)

if now - idle_start < idle_period:
busy.append("{} [{}] {}: {}".format(unit.name,
unit.agent_status,
unit.workload_status,
unit.workload_status_message))
_raise_for_status(errors, "error")
_raise_for_status(blocks, "blocked")

if not busy:
break
else:
# FIXME key in dict
idle_times.pop(unit.name, None)
# FIXME bits in FullStatus
busy.append("{} [{}] {}: {}".format(unit.name,
unit.agent_status,
unit.workload_status,
unit.workload_status_message))
_raise_for_status(errors, "error")
_raise_for_status(blocks, "blocked")

if not busy:
return True

if timeout is not None and datetime.now() - start_time > timeout:
raise jasyncio.TimeoutError("\n ".join(["Timed out waiting for model:", *busy]))
if timeout is not None and datetime.now() - start_time > timeout:
raise jasyncio.TimeoutError("\n ".join(["Timed out waiting for model:", *busy]))

if last_log_time is None or datetime.now() - last_log_time > log_interval:
log.info("\n ".join(["Waiting for model:", *busy]))
last_log_time = datetime.now()
if last_log_time[0] is None or datetime.now() - last_log_time[0] > log_interval:
log.info("\n ".join(["Waiting for model:", *busy]))
last_log_time[0] = datetime.now()

await jasyncio.sleep(check_freq)
return False


def _raise_for_status(entities: Dict, status: Any) -> None:
Expand Down

0 comments on commit d73db1d

Please sign in to comment.