Use FastAPI Lifespan Events

This commit is contained in:
grossmj 2026-03-28 09:54:22 +08:00
parent 89d665f22b
commit 01a68fb23a
No known key found for this signature in database
GPG Key ID: 1E7DD6DBB53FF3D7
2 changed files with 55 additions and 54 deletions

View File

@ -46,7 +46,6 @@ from gns3server.controller.controller_error import (
from gns3server.api.routes import controller, index from gns3server.api.routes import controller, index
from gns3server.api.routes.compute import compute_api from gns3server.api.routes.compute import compute_api
from gns3server.core import tasks from gns3server.core import tasks
from gns3server.version import __version__
import logging import logging
@ -56,6 +55,7 @@ log = logging.getLogger(__name__)
def get_application() -> FastAPI: def get_application() -> FastAPI:
application = FastAPI( application = FastAPI(
lifespan=tasks.lifespan,
title="GNS3 controller API", title="GNS3 controller API",
description="This page describes the public controller API for GNS3", description="This page describes the public controller API for GNS3",
version="v3", version="v3",
@ -71,8 +71,8 @@ def get_application() -> FastAPI:
allow_headers=["*"], allow_headers=["*"],
) )
application.add_event_handler("startup", tasks.create_startup_handler(application)) #application.add_event_handler("startup", tasks.create_startup_handler(application))
application.add_event_handler("shutdown", tasks.create_shutdown_handler(application)) #application.add_event_handler("shutdown", tasks.create_shutdown_handler(application))
application.include_router(index.router, tags=["Index"]) application.include_router(index.router, tags=["Index"])
application.include_router(controller.router, prefix="/v3") application.include_router(controller.router, prefix="/v3")
application.mount("/static", StaticFiles(packages=[('gns3server', 'static')]), name="static") application.mount("/static", StaticFiles(packages=[('gns3server', 'static')]), name="static")

View File

@ -17,8 +17,8 @@
import asyncio import asyncio
from typing import Callable
from fastapi import FastAPI from fastapi import FastAPI
from contextlib import asynccontextmanager
from gns3server.controller import Controller from gns3server.controller import Controller
from gns3server.config import Config from gns3server.config import Config
@ -35,74 +35,75 @@ log = logging.getLogger(__name__)
auto_discover_images_task_handle = None auto_discover_images_task_handle = None
def create_startup_handler(app: FastAPI) -> Callable: @asynccontextmanager
async def lifespan(app: FastAPI):
await startup(app)
yield
await shutdown(app)
async def startup(app: FastAPI) -> None:
""" """
Tasks to be performed when the server is starting. Tasks to be performed when the server is starting.
""" """
async def start_app() -> None: loop = asyncio.get_event_loop()
loop = asyncio.get_event_loop() logger = logging.getLogger("asyncio")
logger = logging.getLogger("asyncio") logger.setLevel(logging.ERROR)
logger.setLevel(logging.ERROR)
if log.getEffectiveLevel() == logging.DEBUG: if log.getEffectiveLevel() == logging.DEBUG:
# On debug version we enable info that # On debug version we enable info that
# coroutine is not called in a way await/await # coroutine is not called in a way await/await
loop.set_debug(True) loop.set_debug(True)
# connect to the database # connect to the database
await connect_to_db(app) await connect_to_db(app)
# retrieve the computes from the database # retrieve the computes from the database
computes = await get_computes(app) computes = await get_computes(app)
await Controller.instance().start(computes) await Controller.instance().start(computes)
# Because with a large image collection # Because with a large image collection
# without md5sum already computed we start the # without md5sum already computed we start the
# computing with server start # computing with server start
from gns3server.compute.qemu import Qemu from gns3server.compute.qemu import Qemu
if Config.instance().settings.Server.auto_discover_images is True: if Config.instance().settings.Server.auto_discover_images is True:
# Start the discovering new images on file system 5 seconds after the server has started # Start the discovering new images on file system 5 seconds after the server has started
# to give it a chance to process API requests # to give it a chance to process API requests
global auto_discover_images_task_handle global auto_discover_images_task_handle
auto_discover_images_task_handle = asyncio.get_event_loop().call_later( auto_discover_images_task_handle = asyncio.get_event_loop().call_later(
5, 5,
lambda: asyncio.create_task(discover_images_on_filesystem(app)) lambda: asyncio.create_task(discover_images_on_filesystem(app))
) )
for module in MODULES: for module in MODULES:
log.debug(f"Loading module {module.__name__}") log.debug(f"Loading module {module.__name__}")
m = module.instance() m = module.instance()
m.port_manager = PortManager.instance() m.port_manager = PortManager.instance()
return start_app
def create_shutdown_handler(app: FastAPI) -> Callable: async def shutdown(app: FastAPI) -> None:
""" """
Tasks to be performed when the server is exiting. Tasks to be performed when the server is exiting.
""" """
async def shutdown_handler() -> None: if auto_discover_images_task_handle is not None and not auto_discover_images_task_handle.cancelled():
auto_discover_images_task_handle.cancel()
await HTTPClient.close_session()
await Controller.instance().stop()
if auto_discover_images_task_handle is not None and not auto_discover_images_task_handle.cancelled(): for module in MODULES:
auto_discover_images_task_handle.cancel() log.debug(f"Unloading module {module.__name__}")
await HTTPClient.close_session() m = module.instance()
await Controller.instance().stop() await m.unload()
for module in MODULES: if PortManager.instance().tcp_ports:
log.debug(f"Unloading module {module.__name__}") log.warning(f"TCP ports are still used {PortManager.instance().tcp_ports}")
m = module.instance()
await m.unload()
if PortManager.instance().tcp_ports: if PortManager.instance().udp_ports:
log.warning(f"TCP ports are still used {PortManager.instance().tcp_ports}") log.warning(f"UDP ports are still used {PortManager.instance().udp_ports}")
if PortManager.instance().udp_ports: await disconnect_from_db(app)
log.warning(f"UDP ports are still used {PortManager.instance().udp_ports}")
await disconnect_from_db(app)
return shutdown_handler