@@ -1436,6 +1436,108 @@ async def test_updates_ssh_fleet(self, test_db, session: AsyncSession, client: A
14361436 assert instance .status == InstanceStatus .PENDING
14371437 assert instance .remote_connection_info is not None
14381438
1439+ @pytest .mark .asyncio
1440+ @pytest .mark .parametrize ("test_db" , ["sqlite" , "postgres" ], indirect = True )
1441+ async def test_updates_cloud_fleet_nodes_in_place_when_fleet_in_use (
1442+ self , test_db , session : AsyncSession , client : AsyncClient
1443+ ):
1444+ user = await create_user (session , global_role = GlobalRole .USER )
1445+ project = await create_project (session )
1446+ await add_project_member (
1447+ session = session , project = project , user = user , project_role = ProjectRole .USER
1448+ )
1449+ current_spec = get_fleet_spec (
1450+ conf = get_fleet_configuration (nodes = FleetNodesSpec (min = 0 , target = 0 , max = 2 ))
1451+ )
1452+ fleet = await create_fleet (session = session , project = project , spec = current_spec )
1453+ repo = await create_repo (session = session , project_id = project .id )
1454+ run = await create_run (session = session , project = project , repo = repo , user = user , fleet = fleet )
1455+ job = await create_job (session = session , run = run , fleet = fleet )
1456+ instance = await create_instance (
1457+ session = session ,
1458+ project = project ,
1459+ fleet = fleet ,
1460+ job = job ,
1461+ status = InstanceStatus .BUSY ,
1462+ instance_num = 0 ,
1463+ )
1464+ spec = current_spec .copy (deep = True )
1465+ spec .configuration .nodes = FleetNodesSpec (min = 1 , target = 1 , max = 3 )
1466+
1467+ response = await client .post (
1468+ f"/api/project/{ project .name } /fleets/apply" ,
1469+ headers = get_auth_headers (user .token ),
1470+ json = {
1471+ "plan" : {
1472+ "spec" : spec .dict (),
1473+ "current_resource" : _fleet_model_to_json_dict (fleet ),
1474+ },
1475+ "force" : False ,
1476+ },
1477+ )
1478+
1479+ response_json = response .json ()
1480+ assert response .status_code == 200 , response_json
1481+ assert response_json ["id" ] == str (fleet .id )
1482+ assert response_json ["spec" ]["configuration" ]["nodes" ] == {"min" : 1 , "max" : 3 }
1483+
1484+ await session .refresh (fleet )
1485+ await session .refresh (instance )
1486+ assert json .loads (fleet .spec )["configuration" ]["nodes" ] == {"min" : 1 , "max" : 3 }
1487+ assert instance .status == InstanceStatus .BUSY
1488+
1489+ @pytest .mark .asyncio
1490+ @pytest .mark .parametrize ("test_db" , ["sqlite" , "postgres" ], indirect = True )
1491+ async def test_updates_cloud_fleet_nodes_target_without_changing_instance_count (
1492+ self , test_db , session : AsyncSession , client : AsyncClient
1493+ ):
1494+ user = await create_user (session , global_role = GlobalRole .USER )
1495+ project = await create_project (session )
1496+ await add_project_member (
1497+ session = session , project = project , user = user , project_role = ProjectRole .USER
1498+ )
1499+ current_spec = get_fleet_spec (
1500+ conf = get_fleet_configuration (nodes = FleetNodesSpec (min = 0 , target = 0 , max = 1 ))
1501+ )
1502+ fleet = await create_fleet (session = session , project = project , spec = current_spec )
1503+ spec = current_spec .copy (deep = True )
1504+ spec .configuration .nodes = FleetNodesSpec (min = 0 , target = 1 , max = 1 )
1505+
1506+ response = await client .post (
1507+ f"/api/project/{ project .name } /fleets/apply" ,
1508+ headers = get_auth_headers (user .token ),
1509+ json = {
1510+ "plan" : {
1511+ "spec" : spec .dict (),
1512+ "current_resource" : _fleet_model_to_json_dict (fleet ),
1513+ },
1514+ "force" : False ,
1515+ },
1516+ )
1517+
1518+ response_json = response .json ()
1519+ assert response .status_code == 200 , response_json
1520+ assert response_json ["id" ] == str (fleet .id )
1521+ assert response_json ["spec" ]["configuration" ]["nodes" ] == {
1522+ "min" : 0 ,
1523+ "target" : 1 ,
1524+ "max" : 1 ,
1525+ }
1526+
1527+ await session .refresh (fleet )
1528+ assert json .loads (fleet .spec )["configuration" ]["nodes" ] == {
1529+ "min" : 0 ,
1530+ "target" : 1 ,
1531+ "max" : 1 ,
1532+ }
1533+ res = await session .execute (
1534+ select (InstanceModel ).where (
1535+ InstanceModel .fleet_id == fleet .id ,
1536+ InstanceModel .deleted == False ,
1537+ )
1538+ )
1539+ assert list (res .scalars ().all ()) == []
1540+
14391541 @pytest .mark .asyncio
14401542 @pytest .mark .parametrize ("test_db" , ["sqlite" , "postgres" ], indirect = True )
14411543 @freeze_time (datetime (2023 , 1 , 2 , 3 , 4 , tzinfo = timezone .utc ))
@@ -2118,6 +2220,62 @@ async def test_returns_update_plan_for_existing_fleet(
21182220 "action" : "update" ,
21192221 }
21202222
2223+ @pytest .mark .asyncio
2224+ @pytest .mark .parametrize ("test_db" , ["sqlite" , "postgres" ], indirect = True )
2225+ async def test_returns_update_plan_for_existing_cloud_fleet_nodes_update (
2226+ self , test_db , session : AsyncSession , client : AsyncClient
2227+ ):
2228+ user = await create_user (session = session , global_role = GlobalRole .USER )
2229+ project = await create_project (session = session , owner = user )
2230+ await add_project_member (
2231+ session = session , project = project , user = user , project_role = ProjectRole .USER
2232+ )
2233+ current_spec = get_fleet_spec (
2234+ conf = get_fleet_configuration (nodes = FleetNodesSpec (min = 0 , target = 0 , max = 1 ))
2235+ )
2236+ spec = current_spec .copy (deep = True )
2237+ spec .configuration .nodes = FleetNodesSpec (min = 1 , target = 1 , max = 1 )
2238+ fleet = await create_fleet (session = session , project = project , spec = current_spec )
2239+
2240+ response = await client .post (
2241+ f"/api/project/{ project .name } /fleets/get_plan" ,
2242+ headers = get_auth_headers (user .token ),
2243+ json = {"spec" : spec .dict ()},
2244+ )
2245+
2246+ response_json = response .json ()
2247+ assert response .status_code == 200 , response_json
2248+ assert response_json ["current_resource" ]["id" ] == str (fleet .id )
2249+ assert response_json ["action" ] == "update"
2250+
2251+ @pytest .mark .asyncio
2252+ @pytest .mark .parametrize ("test_db" , ["sqlite" , "postgres" ], indirect = True )
2253+ async def test_returns_create_plan_for_existing_cloud_fleet_blocks_update (
2254+ self , test_db , session : AsyncSession , client : AsyncClient
2255+ ):
2256+ user = await create_user (session = session , global_role = GlobalRole .USER )
2257+ project = await create_project (session = session , owner = user )
2258+ await add_project_member (
2259+ session = session , project = project , user = user , project_role = ProjectRole .USER
2260+ )
2261+ current_spec = get_fleet_spec (
2262+ conf = get_fleet_configuration (nodes = FleetNodesSpec (min = 0 , target = 0 , max = 1 ))
2263+ )
2264+ spec = current_spec .copy (deep = True )
2265+ spec .configuration .blocks = 2
2266+ fleet = await create_fleet (session = session , project = project , spec = current_spec )
2267+
2268+ response = await client .post (
2269+ f"/api/project/{ project .name } /fleets/get_plan" ,
2270+ headers = get_auth_headers (user .token ),
2271+ json = {"spec" : spec .dict ()},
2272+ )
2273+
2274+ response_json = response .json ()
2275+ assert response .status_code == 200 , response_json
2276+ assert response_json ["current_resource" ]["id" ] == str (fleet .id )
2277+ assert response_json ["action" ] == "create"
2278+
21212279 @pytest .mark .asyncio
21222280 @pytest .mark .parametrize ("test_db" , ["sqlite" , "postgres" ], indirect = True )
21232281 async def test_returns_create_plan_for_existing_fleet (
0 commit comments