-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: Replace Jupyter file-shares with generic project level volumes
- Loading branch information
1 parent
aedb237
commit c1f984a
Showing
24 changed files
with
496 additions
and
62 deletions.
There are no files selected for viewing
113 changes: 113 additions & 0 deletions
113
backend/capellacollab/alembic/versions/b2fd698ed6cb_add_general_file_shares.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
"""Add general file-shares | ||
Revision ID: b2fd698ed6cb | ||
Revises: ca9ce61491a7 | ||
Create Date: 2025-02-21 15:41:21.391386 | ||
""" | ||
|
||
import datetime | ||
import logging | ||
|
||
import sqlalchemy as sa | ||
from alembic import op | ||
from sqlalchemy.dialects import postgresql | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
# revision identifiers, used by Alembic. | ||
revision = "b2fd698ed6cb" | ||
down_revision = "ca9ce61491a7" | ||
branch_labels = None | ||
depends_on = None | ||
|
||
t_tool_models = sa.Table( | ||
"tool_models", | ||
sa.MetaData(), | ||
sa.Column("id", sa.Integer(), primary_key=True), | ||
sa.Column("project_id", sa.Integer()), | ||
sa.Column("tool_id"), | ||
sa.Column("configuration", postgresql.JSONB(astext_type=sa.Text())), | ||
) | ||
|
||
t_tools = sa.Table( | ||
"tools", | ||
sa.MetaData(), | ||
sa.Column("id", sa.Integer()), | ||
sa.Column("integrations", postgresql.JSONB(astext_type=sa.Text())), | ||
) | ||
|
||
|
||
def upgrade(): | ||
t_project_volumes = op.create_table( | ||
"project_volumes", | ||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), | ||
sa.Column("created_at", sa.DateTime(), nullable=False), | ||
sa.Column("pvc_name", sa.String(), nullable=False), | ||
sa.Column("size", sa.String(), nullable=False), | ||
sa.Column("project_id", sa.Integer(), nullable=False), | ||
sa.ForeignKeyConstraint( | ||
["project_id"], | ||
["projects.id"], | ||
), | ||
sa.PrimaryKeyConstraint("id"), | ||
sa.UniqueConstraint("pvc_name"), | ||
) | ||
op.create_index( | ||
op.f("ix_project_volumes_id"), "project_volumes", ["id"], unique=False | ||
) | ||
|
||
connection = op.get_bind() | ||
tools = connection.execute(sa.select(t_tools)).mappings().all() | ||
|
||
jupyter_tool_ids = [] | ||
for tool in tools: | ||
if tool["integrations"]["jupyter"]: | ||
jupyter_tool_ids.append(tool["id"]) | ||
|
||
tool_models = ( | ||
connection.execute( | ||
sa.select(t_tool_models).where( | ||
t_tool_models.c.tool_id.in_(jupyter_tool_ids) | ||
) | ||
) | ||
.mappings() | ||
.all() | ||
) | ||
|
||
grouped_by_project = {} | ||
for tool_model in tool_models: | ||
logger.info( | ||
"Adding general file share for Jupyter model %s in project %s", | ||
tool_model["id"], | ||
tool_model["project_id"], | ||
) | ||
|
||
project_id = tool_model["project_id"] | ||
if project_id in grouped_by_project: | ||
raise ValueError( | ||
"Due a removal of the abstraction layer for" | ||
" Jupyter file-shares, it's no longer possible to" | ||
" have multiple file-shares in one project." | ||
f" In the project {project_id} we've identified multiple Jupyter models." | ||
" Please rollback, merge the files in the file-shares manually" | ||
" and remove empty Jupyter models." | ||
" Then try again." | ||
) | ||
|
||
if "workspace" in tool_model["configuration"]: | ||
grouped_by_project[project_id] = { | ||
"created_at": datetime.datetime.now(tz=datetime.UTC), | ||
"pvc_name": "shared-workspace-" | ||
+ tool_model["configuration"]["workspace"], | ||
"project_id": tool_model["project_id"], | ||
"size": "2Gi", | ||
} | ||
|
||
op.bulk_insert( | ||
t_project_volumes, | ||
list(grouped_by_project.values()), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors | ||
# SPDX-License-Identifier: Apache-2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
from fastapi import status | ||
|
||
from capellacollab.core import exceptions | ||
|
||
|
||
class OnlyOneVolumePerProjectError(exceptions.BaseError): | ||
def __init__(self, project_slug: str): | ||
super().__init__( | ||
status_code=status.HTTP_400_BAD_REQUEST, | ||
title="One one volume per project allowed", | ||
reason=( | ||
f"You can't add another volume to the project '{project_slug}'." | ||
" Each project can only have a maximum of one volume." | ||
" Reuse the existing volume or delete the old volume first." | ||
), | ||
err_code="ONLY_ONE_VOLUME_PER_PROJECT", | ||
) | ||
|
||
@classmethod | ||
def openapi_example(cls) -> "OnlyOneVolumePerProjectError": | ||
return cls("test") | ||
|
||
|
||
class VolumeNotFoundError(exceptions.BaseError): | ||
def __init__(self, volume_id: int, project_slug: str): | ||
super().__init__( | ||
status_code=status.HTTP_404_NOT_FOUND, | ||
title="Project volume not found", | ||
reason=( | ||
f"Couldn't find the volume {volume_id} in the project '{project_slug}'." | ||
), | ||
err_code="PROJECT_VOLUME_NOT_FOUND", | ||
) | ||
|
||
@classmethod | ||
def openapi_example(cls) -> "VolumeNotFoundError": | ||
return cls(-1, "test") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
import datetime | ||
|
||
import pydantic | ||
import sqlalchemy as sa | ||
from sqlalchemy import orm | ||
|
||
from capellacollab.core import database | ||
from capellacollab.core import pydantic as core_pydantic | ||
from capellacollab.projects import models as projects_models | ||
|
||
|
||
class ProjectVolume(core_pydantic.BaseModel): | ||
created_at: datetime.datetime | ||
size: str | ||
pvc_name: str | ||
|
||
_validate_created_at = pydantic.field_serializer("created_at")( | ||
core_pydantic.datetime_serializer | ||
) | ||
|
||
|
||
class DatabaseProjectVolume(database.Base): | ||
__tablename__ = "project_volumes" | ||
|
||
id: orm.Mapped[int] = orm.mapped_column( | ||
init=False, primary_key=True, index=True, autoincrement=True | ||
) | ||
|
||
created_at: orm.Mapped[datetime.datetime] | ||
pvc_name: orm.Mapped[str] = orm.mapped_column(unique=True) | ||
size: orm.Mapped[str] | ||
|
||
project_id: orm.Mapped[int] = orm.mapped_column( | ||
sa.ForeignKey("projects.id"), init=False | ||
) | ||
project: orm.Mapped[projects_models.DatabaseProject] = orm.relationship() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
import fastapi | ||
|
||
from capellacollab.permissions import models as permissions_models | ||
from capellacollab.projects.permissions import ( | ||
injectables as projects_permissions_injectables, | ||
) | ||
from capellacollab.projects.permissions import ( | ||
models as projects_permissions_models, | ||
) | ||
|
||
from . import models | ||
|
||
router = fastapi.APIRouter( | ||
prefix="/volumes", | ||
tags=["Projects - Volumes"], | ||
) | ||
|
||
|
||
@router.get( | ||
"", | ||
dependencies=[ | ||
fastapi.Depends( | ||
projects_permissions_injectables.ProjectPermissionValidation( | ||
required_scope=projects_permissions_models.ProjectUserScopes( | ||
project_users={permissions_models.UserTokenVerb.GET} | ||
) | ||
) | ||
) | ||
], | ||
) | ||
def get_project_volumes() -> models.ProjectVolume: | ||
return [] | ||
|
||
|
||
@router.get( | ||
"", | ||
dependencies=[ | ||
fastapi.Depends( | ||
projects_permissions_injectables.ProjectPermissionValidation( | ||
required_scope=projects_permissions_models.ProjectUserScopes( | ||
project_users={permissions_models.UserTokenVerb.CREATE} | ||
) | ||
) | ||
) | ||
], | ||
) | ||
def create_project_volume(): | ||
return {} | ||
|
||
|
||
@router.get( | ||
"", | ||
dependencies=[ | ||
fastapi.Depends( | ||
projects_permissions_injectables.ProjectPermissionValidation( | ||
required_scope=projects_permissions_models.ProjectUserScopes( | ||
project_users={permissions_models.UserTokenVerb.DELETE} | ||
) | ||
) | ||
) | ||
], | ||
) | ||
def delete_project_volume(): | ||
return {} |
Oops, something went wrong.