Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#570 Implement default env creation #605

Merged
merged 15 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 13 additions & 17 deletions agenta-backend/agenta_backend/main.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import os
import json
from fastapi import FastAPI
import os
from contextlib import asynccontextmanager

from agenta_backend.config import settings
from agenta_backend.routers import app_variant
from agenta_backend.routers import testset_router
from fastapi.middleware.cors import CORSMiddleware
from agenta_backend.routers import container_router
from agenta_backend.routers import evaluation_router
from agenta_backend.services.db_manager import (
add_template,
remove_old_template_from_db,
)
from agenta_backend.services.container_manager import (
pull_image_from_docker_hub,
from agenta_backend.routers import (
app_variant,
container_router,
environment_router,
evaluation_router,
testset_router,
)
from agenta_backend.services.cache_manager import (
retrieve_templates_from_dockerhub_cached,
retrieve_templates_info_from_dockerhub_cached,
)

from contextlib import asynccontextmanager
from agenta_backend.config import settings

from agenta_backend.services.container_manager import pull_image_from_docker_hub
from agenta_backend.services.db_manager import add_template, remove_old_template_from_db
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

origins = [
"http://localhost:3000",
Expand Down Expand Up @@ -89,6 +84,7 @@ async def lifespan(application: FastAPI, cache=True):
app.include_router(evaluation_router.router, prefix="/evaluations")
app.include_router(testset_router.router, prefix="/testsets")
app.include_router(container_router.router, prefix="/containers")
app.include_router(environment_router.router, prefix="/environments")

allow_headers = ["Content-Type"]

Expand Down
8 changes: 7 additions & 1 deletion agenta-backend/agenta_backend/models/api/api_models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datetime import datetime
from typing import Any, Dict, List, Optional

from pydantic import BaseModel
from typing import List, Optional, Dict, Any


class AppVariant(BaseModel):
Expand Down Expand Up @@ -60,3 +61,8 @@ class CreateAppVariant(BaseModel):
image_id: str
image_tag: str
env_vars: Dict[str, str]


class Environment(BaseModel):
name: str
deployed_app_variant: Optional[str]
15 changes: 13 additions & 2 deletions agenta-backend/agenta_backend/models/db_models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datetime import datetime
from typing import Optional, Dict, Any, List
from odmantic import Field, Model, Reference, EmbeddedModel
from typing import Any, Dict, List, Optional

from odmantic import EmbeddedModel, Field, Model, Reference


class OrganizationDB(Model):
Expand Down Expand Up @@ -49,6 +50,16 @@ class Config:
collection = "app_variants"


class EnvironmentDB(Model):
name: str
user_id: UserDB = Reference(key_name="user")
app_name: str
deployed_app_variant: Optional[str]

class Config:
collection = "environments"


class TemplateDB(Model):
template_id: int
name: str
Expand Down
59 changes: 59 additions & 0 deletions agenta-backend/agenta_backend/routers/environment_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import os
from typing import List

from agenta_backend.models.api.api_models import Environment
from agenta_backend.services import db_manager
from fastapi import APIRouter, Depends, HTTPException

if os.environ["FEATURE_FLAG"] in ["cloud", "ee", "demo"]:
from agenta_backend.ee.services.auth_helper import SessionContainer, verify_session
from agenta_backend.ee.services.selectors import get_user_and_org_id
else:
from agenta_backend.services.auth_helper import SessionContainer, verify_session
from agenta_backend.services.selectors import get_user_and_org_id


router = APIRouter()


@router.get("/", response_model=List[Environment])
async def list_environments(
app_name: str,
stoken_session: SessionContainer = Depends(verify_session()),
):
"""
Lists the environments for the given app.
"""
try:
kwargs: dict = await get_user_and_org_id(stoken_session)
app_variants = await db_manager.list_environments(app_name=app_name, **kwargs)
return app_variants
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))


@router.post("/deploy/")
async def deploy_to_environment(
environment_name: str,
app_name: str,
variant_name: str,
stoken_session: SessionContainer = Depends(verify_session()),
):
"""Deploys a given variant to an environment.

Args:
environment_name: Name of the environment to deploy to.
app_name: Name of the app to deploy.
variant_name: Name of the variant to deploy.
stoken_session: . Defaults to Depends(verify_session()).

Raises:
HTTPException: If the deployment fails.
"""
try:
kwargs: dict = await get_user_and_org_id(stoken_session)
await db_manager.deploy_to_environment(
app_name, environment_name, variant_name, **kwargs
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
122 changes: 83 additions & 39 deletions agenta-backend/agenta_backend/services/app_manager.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
"""Main Business logic
"""
import os
import logging
from typing import Optional
import os
from typing import List, Optional

from agenta_backend.config import settings
from agenta_backend.models.api.api_models import (
URI,
App,
AppVariant,
Image,
DockerEnvVars,
Environment,
Image,
ImageExtended,
)
from agenta_backend.models.db_models import AppVariantDB, TestSetDB
from agenta_backend.services import db_manager, docker_utils
from docker.errors import DockerException


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

Expand Down Expand Up @@ -125,7 +126,7 @@ def _delete_docker_image(image: Image) -> None:
logger.info(f"Image {image.tags} deleted")
except Exception as e:
logger.warning(
f"Warning: Error deleting image {image.tags}. Probably multiple variants using it."
f"Warning: Error deleting image {image.tags}. Probably multiple variants using it.\n {str(e)}"
)


Expand All @@ -147,14 +148,16 @@ async def remove_app_variant(app_variant: AppVariant, **kwargs: dict) -> None:
app_variant_db = await _fetch_app_variant_from_db(app_variant, **kwargs)

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)
error_msg = f"Failed to delete app variant {app_variant.app_name}/{app_variant.variant_name}: Not found in DB."
logger.error(error_msg)
raise ValueError(error_msg)

try:
is_last_variant = await db_manager.check_is_last_variant(app_variant_db)
is_last_variant_for_image = await db_manager.check_is_last_variant_for_image(
app_variant_db
)

if is_last_variant:
if is_last_variant_for_image:
image = await _fetch_image_from_db(app_variant, **kwargs)

if image:
Expand All @@ -168,14 +171,22 @@ async def remove_app_variant(app_variant: AppVariant, **kwargs: dict) -> None:
if os.environ["FEATURE_FLAG"] not in ["cloud", "ee", "demo"]:
_delete_docker_image(image)
else:
print("Debug: Image not found. Skipping deletion.")

logger.debug(
f"Image associated with app variant {app_variant.app_name}/{app_variant.variant_name} not found. Skipping deletion."
)
else:
await db_manager.remove_app_variant(app_variant, **kwargs)

app_variants = await db_manager.list_app_variants(
app_name=app_variant.app_name, **kwargs
)
if len(app_variants) == 0: # this was the last variant for an app
await remove_app_related_resources(app_variant.app_name, **kwargs)
except Exception as e:
logger.error(f"Error deleting app variant: {str(e)}")
raise
logger.error(
f"An error occurred while deleting app variant {app_variant.app_name}/{app_variant.variant_name}: {str(e)}"
)
raise e from None


async def remove_app(app: App, **kwargs: dict):
Expand All @@ -190,32 +201,65 @@ async def remove_app(app: App, **kwargs: dict):
app_name = app.app_name
apps = await db_manager.list_apps(**kwargs)
if app_name not in [app.app_name for app in apps]:
msg = f"App {app_name} not found in DB"
logger.error(msg)
raise ValueError(msg)
error_msg = f"Failed to delete app {app_name}: Not found in DB."
logger.error(error_msg)
raise ValueError(error_msg)
try:
app_variants = await db_manager.list_app_variants(app_name=app_name, **kwargs)
except Exception as e:
logger.error(f"Error fetching app variants from the database: {str(e)}")
raise
raise e from None

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:
await remove_app_variant(app_variant, **kwargs)
logger.info(
f"App variant {app_variant.app_name}/{app_variant.variant_name} deleted"
)
error_msg = (
f"Failed to fetch app variants for app {app_name}: No variants found in DB."
)
logger.error(error_msg)
raise ValueError(error_msg)

try:
# Delete associated variants
for app_variant in app_variants:
await remove_app_variant(app_variant, **kwargs)
logger.info(
f"Successfully deleted app variant {app_variant.app_name}/{app_variant.variant_name}."
)

await remove_app_related_resources(app_name, **kwargs)
except Exception as e:
logger.error(
f"An error occurred while deleting app {app_name} and its associated resources: {str(e)}"
)
raise e from None

await remove_app_testsets(app_name, **kwargs)
logger.info(f"Tatasets for {app_name} app deleted")
except Exception as e:
logger.error(f"Error deleting app variants: {str(e)}")
raise

async def remove_app_related_resources(app_name: str, **kwargs: dict):
"""Removes environments and testsets associated with an app after its deletion.

When an app or its last variant is deleted, this function ensures that
all related resources such as environments and testsets are also deleted.

Args:
app_name: The name of the app whose associated resources are to be removed.
"""
try:
# Delete associated environments
environments: List[Environment] = await db_manager.list_environments(
app_name, **kwargs
)
for environment in environments:
await db_manager.remove_environment(environment.name, app_name, **kwargs)
logger.info(
f"Successfully deleted environment {environment.name} associated with app {app_name}."
)
# Delete associated testsets
await remove_app_testsets(app_name, **kwargs)
logger.info(f"Successfully deleted test sets associated with app {app_name}.")
except Exception as e:
logger.error(
f"An error occurred while cleaning up resources for app {app_name}: {str(e)}"
)
raise e from None


async def remove_app_testsets(app_name: str, **kwargs):
Expand Down Expand Up @@ -315,22 +359,22 @@ async def update_variant_parameters(app_variant: AppVariant, **kwargs: dict):
"",
None,
]:
msg = f"App name and variant name cannot be empty"
msg = "App name and variant name cannot be empty"
logger.error(msg)
raise ValueError(msg)
if app_variant.parameters is None:
msg = f"Parameters cannot be empty when updating app variant"
msg = "Parameters cannot be empty when updating app variant"
logger.error(msg)
raise ValueError(msg)
try:
await db_manager.update_variant_parameters(
app_variant, app_variant.parameters, **kwargs
)
except:
except Exception as e:
logger.error(
f"Error updating app variant {app_variant.app_name}/{app_variant.variant_name}"
)
raise
raise e from None


async def update_variant_image(app_variant: AppVariant, image: Image, **kwargs: dict):
Expand Down Expand Up @@ -391,5 +435,5 @@ async def update_variant_image(app_variant: AppVariant, image: Image, **kwargs:
f"Starting variant {app_variant.app_name}/{app_variant.variant_name}"
)
await start_variant(app_variant, **kwargs)
except:
raise
except Exception as e:
raise e from None
Loading