Skip to content

Commit

Permalink
Merge pull request #39 from Agenta-AI/delete_apps_appvariants
Browse files Browse the repository at this point in the history
Delete apps appvariants
  • Loading branch information
mmabrouk authored May 30, 2023
2 parents 5047396 + df0a00e commit 1299235
Show file tree
Hide file tree
Showing 13 changed files with 544 additions and 214 deletions.
2 changes: 1 addition & 1 deletion agenta-backend/agenta_backend/models/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


def app_variant_db_to_pydantic(app_variant_db: AppVariantDB, previous_variant_name: str = None) -> AppVariant:
return AppVariant(app_name=app_variant_db.app_name, variant_name=app_variant_db.variant_name, parameters=app_variant_db.parameters, previous_variant_name=previous_variant_name)
return AppVariant(app_name=app_variant_db.app_name, variant_name=app_variant_db.variant_name, parameters=app_variant_db.parameters, previous_variant_name=app_variant_db.previous_variant_name)


def image_db_to_pydantic(image_db: ImageDB) -> Image:
Expand Down
4 changes: 2 additions & 2 deletions agenta-backend/agenta_backend/models/db_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ class AppVariantDB(SQLModel, table=True):
variant_name: str = Field(...)
image_id: int = Field(foreign_key="imagedb.id")
parameters: Dict = Field(sa_column=Column(JSON))
previous_variant_id: Optional[int] = Field(default=None, foreign_key="appvariantdb.id")
version: int = Field(default=1)
previous_variant_name: Optional[str] = Field(default=None)
is_deleted: bool = Field(default=False) # soft deletion for using the template variants
48 changes: 38 additions & 10 deletions agenta-backend/agenta_backend/routers/app_variant.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@

from agenta_backend.config import settings
from agenta_backend.models.api.api_models import URI, App, AppVariant, Image
from agenta_backend.services import db_manager, docker_utils
from fastapi import APIRouter, HTTPException, Body
from agenta_backend.services import app_manager, db_manager, docker_utils
from docker.errors import DockerException
from fastapi import APIRouter, Body, HTTPException
from sqlalchemy.exc import SQLAlchemyError

router = APIRouter()
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)


@router.get("/list_variants/", response_model=List[AppVariant])
Expand Down Expand Up @@ -42,7 +46,7 @@ async def list_apps() -> List[App]:
List[App]
"""
try:
apps = db_manager.list_app_names()
apps = db_manager.list_apps()
return apps
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
Expand Down Expand Up @@ -98,10 +102,7 @@ async def add_variant_from_previous(previous_app_variant: AppVariant, new_varian
@router.post("/start/")
async def start_variant(app_variant: AppVariant) -> URI:
try:
image: Image = db_manager.get_image(app_variant)
uri: URI = docker_utils.start_container(
image_name=image.tags, app_name=app_variant.app_name, variant_name=app_variant.variant_name)
return uri
return app_manager.start_variant(app_variant)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

Expand Down Expand Up @@ -131,6 +132,7 @@ async def list_images():
@router.delete("/remove_variant/")
async def remove_variant(app_variant: AppVariant):
"""Remove a variant from the server.
In the case it's the last variant using the image, stop the container and remove the image.
Arguments:
app_variant -- AppVariant to remove
Expand All @@ -139,7 +141,33 @@ async def remove_variant(app_variant: AppVariant):
HTTPException: If there is a problem removing the app variant
"""
try:
if not db_manager.remove_app_variant(app_variant):
raise HTTPException(status_code=404, detail="App variant not found")
app_manager.remove_app_variant(app_variant)
except SQLAlchemyError as e:
detail = f"Database error while trying to remove the app variant: {str(e)}"
raise HTTPException(status_code=500, detail=detail)
except DockerException as e:
detail = f"Docker error while trying to remove the app variant: {str(e)}"
raise HTTPException(status_code=500, detail=detail)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
detail = f"Unexpected error while trying to remove the app variant: {str(e)}"
raise HTTPException(status_code=500, detail=detail)


@router.delete("/remove_app/")
async def remove_app(app: App):
"""Remove app, all its variant, containers and images
Arguments:
app -- App to remove
"""
try:
app_manager.remove_app(app)
except SQLAlchemyError as e:
detail = f"Database error while trying to remove the app: {str(e)}"
raise HTTPException(status_code=500, detail=detail)
except DockerException as e:
detail = f"Docker error while trying to remove the app: {str(e)}"
raise HTTPException(status_code=500, detail=detail)
except Exception as e:
detail = f"Unexpected error while trying to remove the app: {str(e)}"
raise HTTPException(status_code=500, detail=detail)
124 changes: 124 additions & 0 deletions agenta-backend/agenta_backend/services/app_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""Main Business logic
"""

from agenta_backend.services import (db_manager, docker_utils)
from agenta_backend.models.api.api_models import (AppVariant, Image, URI, App)
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def remove_app_variant(app_variant: AppVariant):
"""Removes appvariant from db, if it is the last one using an image, then
deletes the image from the db, shutdowns the container, deletes it and remove
the image from the registry
Arguments:
app_variant -- the app variant to remove
"""
# checks if it is the last app variant using its image
try:
app_variant_db = db_manager.get_variant_from_db(app_variant)
except Exception as e:
logger.error(f"Error fetching app variant from the database: {str(e)}")
raise

if app_variant_db is None:
msg = f"App variant {app_variant.app_name}/{app_variant.variant_name} not found in DB"
logger.error(msg)
raise ValueError(msg)
else:
try:
if db_manager.check_is_last_variant(app_variant_db):
image: Image = db_manager.get_image(app_variant)
try:
container_ids = docker_utils.stop_containers_based_on_image(image)
logger.info(f"Containers {container_ids} stopped")
for container_id in container_ids:
docker_utils.delete_container(container_id)
logger.info(f"Container {container_id} deleted")
except Exception as e:
logger.error(f"Error managing Docker resources: {str(e)}")
raise
try:
docker_utils.delete_image(image)
logger.info(f"Image {image.tags} deleted")
except:
logger.warning(f"Warning: Error deleting image {image.tags}. Probably multiple variants using it.")
db_manager.remove_image(image)
db_manager.remove_app_variant(app_variant)
except Exception as e:
logger.error(f"Error deleting app variant: {str(e)}")
raise


def remove_app(app: App):
"""Removes all app variants from db, if it is the last one using an image, then
deletes the image from the db, shutdowns the container, deletes it and remove
the image from the registry
Arguments:
app_name -- the app name to remove
"""
# checks if it is the last app variant using its image
app_name = app.app_name
if app_name not in [app.app_name for app in db_manager.list_apps()]:
msg = f"App {app_name} not found in DB"
logger.error(msg)
raise ValueError(msg)
try:
app_variants = db_manager.list_app_variants(app_name=app_name, show_soft_deleted=True)
except Exception as e:
logger.error(f"Error fetching app variants from the database: {str(e)}")
raise
if app_variants is None:
msg = f"App {app_name} not found in DB"
logger.error(msg)
raise ValueError(msg)
else:
try:
for app_variant in app_variants:
remove_app_variant(app_variant)
except Exception as e:
logger.error(f"Error deleting app variants: {str(e)}")
raise


def start_variant(app_variant: AppVariant) -> URI:
"""
Starts a Docker container for a given app variant.
Fetches the associated image from the database and delegates to a Docker utility function
to start the container. The URI of the started container is returned.
Args:
app_variant (AppVariant): The app variant for which a container is to be started.
Returns:
URI: The URI of the started Docker container.
Raises:
ValueError: If the app variant does not have a corresponding image in the database.
RuntimeError: If there is an error starting the Docker container.
"""
try:
image: Image = db_manager.get_image(app_variant)
except Exception as e:
logger.error(
f"Error fetching image for app variant {app_variant.app_name}/{app_variant.variant_name} from database: {str(e)}")
raise ValueError(
f"Image for app variant {app_variant.app_name}/{app_variant.variant_name} not found in database") from e

try:
uri: URI = docker_utils.start_container(
image_name=image.tags, app_name=app_variant.app_name, variant_name=app_variant.variant_name)
logger.info(
f"Started Docker container for app variant {app_variant.app_name}/{app_variant.variant_name} at URI {uri}")
except Exception as e:
logger.error(
f"Error starting Docker container for app variant {app_variant.app_name}/{app_variant.variant_name}: {str(e)}")
raise RuntimeError(
f"Failed to start Docker container for app variant {app_variant.app_name}/{app_variant.variant_name}") from e

return uri
Loading

0 comments on commit 1299235

Please sign in to comment.