Feature 10239: Distributed VCA
[osm/N2VC.git] / n2vc / juju_watcher.py
index e122786..e206e06 100644 (file)
@@ -35,6 +35,13 @@ def status(application: Application) -> str:
 
 
 def entity_ready(entity: ModelEntity) -> bool:
+    """
+    Check if the entity is ready
+
+    :param: entity: Model entity. It can be a machine, action, or application.
+
+    :returns: boolean saying if the entity is ready or not
+    """
     entity_type = entity.entity_type
     if entity_type == "machine":
         return entity.agent_status in ["started"]
@@ -42,14 +49,33 @@ def entity_ready(entity: ModelEntity) -> bool:
         return entity.status in ["completed", "failed", "cancelled"]
     elif entity_type == "application":
         # Workaround for bug: https://github.com/juju/python-libjuju/issues/441
-        return status(entity) in ["active", "blocked"]
+        return entity.status in ["active", "blocked"]
     else:
         raise EntityInvalidException("Unknown entity type: {}".format(entity_type))
 
 
+def application_ready(application: Application) -> bool:
+    """
+    Check if an application has a leader
+
+    :param: application: Application entity.
+
+    :returns: boolean saying if the application has a unit that is a leader.
+    """
+    ready_status_list = ["active", "blocked"]
+    application_ready = application.status in ready_status_list
+    units_ready = all(
+        unit.workload_status in ready_status_list for unit in application.units
+    )
+    return application_ready and units_ready
+
+
 class JujuModelWatcher:
     @staticmethod
-    async def wait_for_model(model: Model, timeout: float = 3600):
+    async def wait_for_model(
+        model: Model,
+        timeout: float = 3600
+    ):
         """
         Wait for all entities in model to reach its final state.
 
@@ -63,24 +89,32 @@ class JujuModelWatcher:
             timeout = 3600.0
 
         # Coroutine to wait until the entity reaches the final state
-        wait_for_entity = asyncio.ensure_future(
-            asyncio.wait_for(
-                model.block_until(
-                    lambda: all(
-                        entity_ready(entity) for entity in model.applications.values()
-                    )
-                ),
-                timeout=timeout,
+        async def wait_until_model_ready():
+            wait_for_entity = asyncio.ensure_future(
+                asyncio.wait_for(
+                    model.block_until(
+                        lambda: all(
+                            application_ready(application)
+                            for application in model.applications.values()
+                        ),
+                    ),
+                    timeout=timeout,
+                )
             )
-        )
 
-        tasks = [wait_for_entity]
-        try:
-            await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
-        finally:
-            # Cancel tasks
-            for task in tasks:
-                task.cancel()
+            tasks = [wait_for_entity]
+            try:
+                await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
+            finally:
+                # Cancel tasks
+                for task in tasks:
+                    task.cancel()
+
+        await wait_until_model_ready()
+        # Check model is still ready after 10 seconds
+
+        await asyncio.sleep(10)
+        await wait_until_model_ready()
 
     @staticmethod
     async def wait_for(
@@ -90,6 +124,7 @@ class JujuModelWatcher:
         total_timeout: float = 3600,
         db_dict: dict = None,
         n2vc: N2VCConnector = None,
+        vca_id: str = None,
     ):
         """
         Wait for entity to reach its final state.
@@ -100,6 +135,7 @@ class JujuModelWatcher:
         :param: total_timeout:      Timeout for the entity to be active
         :param: db_dict:            Dictionary with data of the DB to write the updates
         :param: n2vc:               N2VC Connector objector
+        :param: vca_id:             VCA ID
 
         :raises: asyncio.TimeoutError when timeout reaches
         """
@@ -116,7 +152,8 @@ class JujuModelWatcher:
         # Coroutine to wait until the entity reaches the final state
         wait_for_entity = asyncio.ensure_future(
             asyncio.wait_for(
-                model.block_until(lambda: entity_ready(entity)), timeout=total_timeout,
+                model.block_until(lambda: entity_ready(entity)),
+                timeout=total_timeout,
             )
         )
 
@@ -129,6 +166,7 @@ class JujuModelWatcher:
                 timeout=progress_timeout,
                 db_dict=db_dict,
                 n2vc=n2vc,
+                vca_id=vca_id,
             )
         )
 
@@ -150,6 +188,7 @@ class JujuModelWatcher:
         timeout: float,
         db_dict: dict = None,
         n2vc: N2VCConnector = None,
+        vca_id: str = None,
     ):
         """
         Observes the changes related to an specific entity in a model
@@ -160,6 +199,7 @@ class JujuModelWatcher:
         :param: timeout:        Maximum time between two updates in the model
         :param: db_dict:        Dictionary with data of the DB to write the updates
         :param: n2vc:           N2VC Connector objector
+        :param: vca_id:         VCA ID
 
         :raises: asyncio.TimeoutError when timeout reaches
         """
@@ -217,6 +257,7 @@ class JujuModelWatcher:
                                 detailed_status=status_message,
                                 vca_status=vca_status,
                                 entity_type=delta_entity,
+                                vca_id=vca_id,
                             )
                 # Check if timeout
                 if time.time() > timeout_end: