From d73db1d7589f29d8a6e950b0ccd9cc9c4198d68e Mon Sep 17 00:00:00 2001 From: Dima Tisnek Date: Wed, 2 Oct 2024 17:49:42 +0900 Subject: [PATCH] chore: wip / squash me --- juju/model.py | 280 +++++++++++++++++++++++++++----------------------- 1 file changed, 152 insertions(+), 128 deletions(-) diff --git a/juju/model.py b/juju/model.py index a3a83d0a..746a5208 100644 --- a/juju/model.py +++ b/juju/model.py @@ -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. @@ -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: