hussein-awala commented on code in PR #75216:
URL: https://github.com/apache/airflow/pull/75216#discussion_r4414159791


##########
airflow-core/src/airflow/api_fastapi/core_api/routes/public/pools.py:
##########
@@ -46,11 +46,32 @@
 )
 from airflow.api_fastapi.core_api.services.public.pools import 
BulkPoolService, update_orm_from_pydantic
 from airflow.api_fastapi.logging.decorators import action_logging
-from airflow.models.pool import Pool
+from airflow.models.pool import Pool, PoolStats
 
 pools_router = AirflowRouter(tags=["Pool"], prefix="/pools")
 
 
+def _build_pool_response(pool: Pool, stats: PoolStats) -> PoolResponse:
+    occupied_slots = stats["running"] + stats["queued"]
+    if pool.include_deferred:
+        occupied_slots += stats["deferred"]
+    return PoolResponse.model_validate(
+        {
+            "pool": pool.pool,
+            "slots": pool.slots,
+            "description": pool.description,
+            "include_deferred": pool.include_deferred,
+            "team_name": pool.team_name,
+            "occupied_slots": occupied_slots,
+            "running_slots": stats["running"],
+            "queued_slots": stats["queued"],
+            "scheduled_slots": stats["scheduled"],
+            "open_slots": stats["open"],

Review Comment:
   Could we calculate open_slots from pool.slots and the occupied_slots 
computed above?
   
   The page query and slots_stats() read the pool configuration separately. 
Under READ COMMITTED, a concurrent capacity change from 10 to 20 (with two 
running slots) can produce slots=10, occupied_slots=2, and open_slots=18 in the 
same response. Changing include_deferred between the reads causes a similar 
inconsistency.
   
   Previously, open_slots() used the same loaded pool configuration. Deriving 
it here would preserve that consistency without another query, while retaining 
the unlimited-pool handling.



##########
airflow-core/src/airflow/api_fastapi/core_api/routes/public/pools.py:
##########
@@ -119,10 +140,22 @@ def get_pools(
         session=session,
     )
 
-    pools = session.scalars(pools_select)
+    pools = session.scalars(pools_select).all()
+    if not pools:
+        return PoolCollectionResponse(pools=[], total_entries=total_entries)
+
+    pool_stats = Pool.slots_stats(

Review Comment:
   Could we preserve the database’s pool-name comparison semantics here?
   
   I confirmed this with real API requests on freshly initialized MySQL. After 
deleting an occupied pool named `analytics` and recreating it as `Analytics`, 
the running task retains its original pool name. SQL comparisons still match it 
under the case-insensitive collation, but `slots_stats()` groups under 
`analytics` and misses the Python dictionary key `Analytics`.
   
   The regression test below produces:
   
   | Endpoint | Running | Occupied | Open |
   |---|---:|---:|---:|
   | Single pool | 2 | 2 | 8 |
   | Pool list | 0 | 0 | 10 |
   
   It **fails on this PR** and **passes when only the previous list 
implementation is restored**. Could the aggregation join against `Pool` and 
group by its canonical name?
   
   Add this test to `TestGetPools` in 
`airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_pools.py`:
   
   ```python
   @pytest.mark.backend("mysql")
   def test_list_counts_slots_after_pool_is_recreated_with_different_case(
       self, test_client, dag_maker, session
   ):
       response = test_client.post("/pools", json={"name": "analytics", 
"slots": 10})
       assert response.status_code == 201
   
       with dag_maker(dag_id="test_recreated_pool_slot_counts", 
session=session):
           EmptyOperator(task_id="running_task", pool="analytics", pool_slots=2)
       ti = dag_maker.create_dagrun().get_task_instances(session=session)[0]
       ti.state = TaskInstanceState.RUNNING
       session.commit()
   
       assert test_client.delete("/pools/analytics").status_code == 204
       response = test_client.post("/pools", json={"name": "Analytics", 
"slots": 10})
       assert response.status_code == 201
   
       single_response = test_client.get("/pools/Analytics")
       assert single_response.status_code == 200
       expected_counts = {"running_slots": 2, "occupied_slots": 2, 
"open_slots": 8}
       single_pool = single_response.json()
       assert {key: single_pool[key] for key in expected_counts} == 
expected_counts
   
       response = test_client.get("/pools", params={"pool_name_pattern": 
"Analytics"})
       assert response.status_code == 200
       pools = response.json()["pools"]
       assert len(pools) == 1
       assert {key: pools[0][key] for key in expected_counts} == expected_counts
   ```
   
   Run in an isolated Breeze MySQL database:
   
   ```bash
   breeze run --backend mysql bash -c '
     airflow db reset --yes &&
     pytest 
airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_pools.py::TestGetPools::test_list_counts_slots_after_pool_is_recreated_with_different_case
 --without-db-init -xvs
   '
   ```
   
   The explicit initialization matters: normal test initialization can use 
historical migrations that create binary-collated pool columns. This reproduces 
the case-insensitive columns created by Airflow’s normal fresh-install path.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to