Skip to content
This repository was archived by the owner on Sep 22, 2023. It is now read-only.

Feature/filebrowser in storage proxy #203

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions changes/203.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implemented client interface for File Browser in Storage Proxy.
11 changes: 6 additions & 5 deletions src/ai/backend/client/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from . import main # noqa
from . import admin # noqa
from . import config # noqa
from . import dotfile # noqa
from . import filebrowser # noqa
from . import main # noqa
from . import server_log # noqa
from . import session # noqa
from . import session_template # noqa
from . import vfolder # noqa
from . import dotfile # noqa
from . import server_log # noqa
from . import admin # noqa
from . import app, logs, proxy, run # noqa
from . import app, logs, proxy, run # noqa
5 changes: 3 additions & 2 deletions src/ai/backend/client/cli/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ def admin():
"""


from . import ( # noqa
from . import manager # noqa
from . import (
agent,
domain,
etcd,
filebrowser,
group,
image,
keypair,
manager,
license,
resource,
resource_policy,
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.
"""
60 changes: 60 additions & 0 deletions src/ai/backend/client/cli/filebrowser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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",
"--vfolder",
help="Vfolder to be attached for a filebrowser session.",
type=str,
metavar="VFOLDER",
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: 2 additions & 1 deletion src/ai/backend/client/func/vfolder.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ async def paginated_list(
async def list_hosts(cls):
rqst = Request('GET', '/folders/_/hosts')
async with rqst.fetch() as resp:
return await resp.json()
res = await resp.json()
return res

@api_function
@classmethod
Expand Down
4 changes: 3 additions & 1 deletion src/ai/backend/client/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ class BaseSession(metaclass=abc.ABCMeta):
'EtcdConfig',
'Resource', 'KeypairResourcePolicy',
'VFolder', 'Dotfile',
'ServerLog',
'ServerLog', 'FileBrowser',
)

aiohttp_session: aiohttp.ClientSession
Expand Down Expand Up @@ -285,6 +285,7 @@ def __init__(
from .func.vfolder import VFolder
from .func.dotfile import Dotfile
from .func.server_log import ServerLog
from .func.filebrowser import FileBrowser

self.System = System
self.Admin = Admin
Expand All @@ -308,6 +309,7 @@ def __init__(
self.VFolder = VFolder
self.Dotfile = Dotfile
self.ServerLog = ServerLog
self.FileBrowser = FileBrowser

@property
def proxy_mode(self) -> bool:
Expand Down
60 changes: 60 additions & 0 deletions tests/test_filebrowser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from unittest import mock

import pytest
from aioresponses import aioresponses

from ai.backend.client.config import API_VERSION
from ai.backend.client.session import Session
from ai.backend.client.test_utils import AsyncMock


def build_url(config, path: str):
base_url = config.endpoint.path.rstrip("/")
query_path = path.lstrip("/") if len(path) > 0 else ""
path = "{0}/{1}".format(base_url, query_path)
canonical_url = config.endpoint.with_path(path)
return canonical_url


@pytest.fixture(scope="module", autouse=True)
def api_version():
mock_nego_func = AsyncMock()
mock_nego_func.return_value = API_VERSION
with mock.patch("ai.backend.client.session._negotiate_api_version", mock_nego_func):
yield


def test_create_vfolder():
host = "local:volume1"
vfolders = ["mydata1"]
with Session() as session, aioresponses() as m:
payload = {
"host": host,
"vfolders": vfolders,
"status": "ok",
"addr": "127.0.0.1",
"container_id": "00000000",
}
m.post(
build_url(session.config, "/storage/filebrowser/create"),
status=201,
payload=payload,
)
resp = session.FileBrowser.create_or_update_browser(host, vfolders)
assert resp == payload


def destroy_browser():
container_id = "0000000"
with Session() as session, aioresponses() as m:
payload = {
"container_id": container_id,
"status": "ok",
}
m.delete(
build_url(session.config, "/storage/filebrowser/destroy"),
status=201,
payload=payload,
)
resp = session.FileBrowser.destroy(container_id)
assert resp == payload