Skip to content

feat: Migrate filebrowser into storage proxy #710

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

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions changes/710.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add support for FileBrowser in Storage Proxy.
Migrate to mono-repository.
1 change: 1 addition & 0 deletions src/ai/backend/client/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from . import config # noqa # type: ignore
from . import dotfile # noqa # type: ignore
from . import extensions # noqa # type: ignore
from . import filebrowser # noqa # type: ignore
from . import model # noqa # type: ignore
from . import server_log # noqa # type: ignore
from . import service # noqa # type: ignore
Expand Down
1 change: 1 addition & 0 deletions src/ai/backend/client/cli/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def admin():
agent,
domain,
etcd,
filebrowser,
group,
image,
keypair,
Expand Down
10 changes: 10 additions & 0 deletions src/ai/backend/client/cli/admin/filebrowser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import annotations

from . import admin


@admin.group()
def filebrowser() -> None:
"""
FileBrowser administration commands.
"""
61 changes: 61 additions & 0 deletions src/ai/backend/client/cli/filebrowser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import sys
from typing import List

import click

from ai.backend.client.session import Session

from .main import main
from .pretty import print_error


@main.group()
def filebrowser():
"""Set of filebrowser operations"""


@filebrowser.command()
@click.option(
"-host",
"--Host",
help="Host:Volume reference for a filebrowser session.",
type=str,
metavar="HOST",
multiple=False,
)
@click.option(
"-vf",
"--vfolders",
help="Vfolder to be attached for a filebrowser session.",
type=str,
metavar="VFOLDERS",
multiple=True,
)
def create(host: str, vfolders: List[str]) -> None:
"""Create or update filebrowser session"""
vfolder = list(vfolders)

with Session() as session:
try:
session.FileBrowser.create_or_update_browser(host, vfolder)
except Exception as e:
print_error(e)
sys.exit(1)


@filebrowser.command()
@click.option(
"-cid",
"--container_id",
help="Container ID of user filebrowser session.",
type=str,
metavar="CID",
)
def destroy(container_id: str) -> None:
"""Destroy filebrowser session using Container ID."""
with Session() as session:
try:
session.FileBrowser.destroy_browser(container_id)
except Exception as e:
print_error(e)
sys.exit(1)
48 changes: 48 additions & 0 deletions src/ai/backend/client/func/filebrowser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import asyncio
import webbrowser

from ..request import Request
from .base import BaseFunction, api_function

__all__ = ("FileBrowser",)


class FileBrowser(BaseFunction):
@api_function
@classmethod
async def create_or_update_browser(self, host: str, vfolders: list[str]) -> str:
rqst = Request("POST", "/storage/filebrowser/create")
rqst.set_json({"host": host, "vfolders": vfolders})
async with rqst.fetch() as resp:
# give a grace period for filebrowser server to initialize and start
await asyncio.sleep(2)
result = await resp.json()
if result["status"] == "ok":
if result["addr"] == "0":
print("the number of container exceeds the maximum limit.")
print(
f"""
File Browser started.
Container ID:
{result['container_id']}
URL: {result['addr']}
""",
)
webbrowser.open_new_tab(result["addr"])
else:
raise Exception
return result

@api_function
@classmethod
async def destroy_browser(self, container_id: str) -> str:
rqst = Request("DELETE", "/storage/filebrowser/destroy")
rqst.set_json({"container_id": container_id})

async with rqst.fetch() as resp:
result = await resp.json()
if result["status"] == "ok":
print("File Browser destroyed.")
else:
raise Exception
return result
3 changes: 3 additions & 0 deletions src/ai/backend/client/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ class BaseSession(metaclass=abc.ABCMeta):
"Resource",
"KeypairResourcePolicy",
"VFolder",
"FileBrowser",
"Dotfile",
"ServerLog",
"Permission",
Expand Down Expand Up @@ -299,6 +300,7 @@ def __init__(
from .func.domain import Domain
from .func.dotfile import Dotfile
from .func.etcd import EtcdConfig
from .func.filebrowser import FileBrowser
from .func.group import Group
from .func.image import Image
from .func.keypair import KeyPair
Expand Down Expand Up @@ -336,6 +338,7 @@ def __init__(
self.ScalingGroup = ScalingGroup
self.SessionTemplate = SessionTemplate
self.VFolder = VFolder
self.FileBrowser = FileBrowser
self.Dotfile = Dotfile
self.ServerLog = ServerLog
self.Permission = Permission
Expand Down
9 changes: 9 additions & 0 deletions src/ai/backend/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import sys
import uuid
from collections import OrderedDict
from contextlib import asynccontextmanager
from datetime import timedelta
from itertools import chain
from typing import TYPE_CHECKING, Any, Iterable, Iterator, Mapping, Tuple, TypeVar, Union
Expand Down Expand Up @@ -283,3 +284,11 @@ async def remove_by_mountpoint(self, mountpoint):
if entry:
return await self.remove_entry(entry)
return False


@asynccontextmanager
async def closing_async(thing):
try:
yield thing
finally:
await thing.close()
Loading