From fadeadc9613f5149e94938d5a1fc34a79f9d46be Mon Sep 17 00:00:00 2001 From: romasku Date: Fri, 12 Feb 2021 17:59:27 +0200 Subject: [PATCH] Named disks (#1983) * Add support of named disk to sdk * Add support of disk name to neuro disk commands * Allow disk names in disk URI in volumes * Allow to create named disks * Adopt endpoints url change * Add changelog * Add displaying of disk names * Fix mounting disk volume by name --- CHANGELOG.D/1983.feature | 4 ++ CLI.md | 5 +- neuro-cli/docs/disk.md | 5 +- neuro-cli/src/neuro_cli/click_types.py | 65 +++++++++++++++++++ neuro-cli/src/neuro_cli/disks.py | 33 +++++++--- neuro-cli/src/neuro_cli/formatters/disks.py | 5 ++ neuro-cli/src/neuro_cli/job.py | 15 ++++- neuro-cli/src/neuro_cli/utils.py | 12 ++++ neuro-cli/tests/e2e/conftest.py | 7 +- neuro-cli/tests/e2e/test_e2e_jobs.py | 31 +++++++++ .../ascii/test_disk_formatter_0.ref | 1 + .../ascii/test_disks_formatter_long_0.ref | 12 ++-- .../ascii/test_disks_formatter_short_0.ref | 12 ++-- neuro-cli/tests/unit/formatters/test_disks.py | 17 ++--- neuro-sdk/docs/disks_reference.rst | 26 ++++++-- neuro-sdk/src/neuro_sdk/disks.py | 16 +++-- neuro-sdk/tests/test_disks.py | 9 ++- 17 files changed, 226 insertions(+), 49 deletions(-) create mode 100644 CHANGELOG.D/1983.feature diff --git a/CHANGELOG.D/1983.feature b/CHANGELOG.D/1983.feature new file mode 100644 index 000000000..5d59afdc1 --- /dev/null +++ b/CHANGELOG.D/1983.feature @@ -0,0 +1,4 @@ +Added support of named: +- create new disk by `neuro disk create --name STORAGE` command +- name can be used to get/delete disk: `neuro disk get ` or `neuro disk delete ` +- name can be used to mount disk: `neuro run -v disk::/mnt/disk ...` diff --git a/CLI.md b/CLI.md index 095572c3b..608529e58 100644 --- a/CLI.md +++ b/CLI.md @@ -2118,6 +2118,7 @@ neuro disk create 500M Name | Description| |----|------------| |_--help_|Show this message and exit.| +|_--name NAME_|Optional disk name| |_\--timeout-unused TIMEDELTA_|Optional disk lifetime limit after last usage in the format '1d2h3m4s' \(some parts may be missing). Set '0' to disable. Default value '1d' can be changed in the user config.| @@ -2130,7 +2131,7 @@ Get disk DISK_ID. **Usage:** ```bash -neuro disk get [OPTIONS] DISK_ID +neuro disk get [OPTIONS] DISK ``` **Options:** @@ -2150,7 +2151,7 @@ Remove disk DISK_ID. **Usage:** ```bash -neuro disk rm [OPTIONS] DISK_IDS... +neuro disk rm [OPTIONS] DISKS... ``` **Options:** diff --git a/neuro-cli/docs/disk.md b/neuro-cli/docs/disk.md index dde9e59dd..419a65493 100644 --- a/neuro-cli/docs/disk.md +++ b/neuro-cli/docs/disk.md @@ -83,6 +83,7 @@ $ neuro disk create 500M | Name | Description | | :--- | :--- | | _--help_ | Show this message and exit. | +| _--name NAME_ | Optional disk name | | _--timeout-unused TIMEDELTA_ | Optional disk lifetime limit after last usage in the format '1d2h3m4s' \(some parts may be missing\). Set '0' to disable. Default value '1d' can be changed in the user config. | @@ -95,7 +96,7 @@ Get disk DISK_ID #### Usage ```bash -neuro disk get [OPTIONS] DISK_ID +neuro disk get [OPTIONS] DISK ``` Get disk `DISK`_ID. @@ -117,7 +118,7 @@ Remove disk DISK_ID #### Usage ```bash -neuro disk rm [OPTIONS] DISK_IDS... +neuro disk rm [OPTIONS] DISKS... ``` Remove disk `DISK`_ID. diff --git a/neuro-cli/src/neuro_cli/click_types.py b/neuro-cli/src/neuro_cli/click_types.py index 787f1e330..7bf5b32d0 100644 --- a/neuro-cli/src/neuro_cli/click_types.py +++ b/neuro-cli/src/neuro_cli/click_types.py @@ -19,6 +19,13 @@ JOB_NAME_REGEX = re.compile(JOB_NAME_PATTERN) JOB_LIMIT_ENV = "NEURO_CLI_JOB_AUTOCOMPLETE_LIMIT" + +# NOTE: these disk name valdation are taken from `platform_disk_api` file `schema.py` +DISK_NAME_MIN_LENGTH = 3 +DISK_NAME_MAX_LENGTH = 40 +DISK_NAME_PATTERN = "^[a-z](?:-?[a-z0-9])*$" +DISK_NAME_REGEX = re.compile(JOB_NAME_PATTERN) + _T = TypeVar("_T") @@ -158,6 +165,32 @@ def convert( JOB_NAME = JobNameType() +class DiskNameType(click.ParamType): + name = "disk_name" + + def convert( + self, value: str, param: Optional[click.Parameter], ctx: Optional[click.Context] + ) -> str: + if ( + len(value) < DISK_NAME_MIN_LENGTH + or len(value) > DISK_NAME_MAX_LENGTH + or DISK_NAME_REGEX.match(value) is None + ): + raise ValueError( + f"Invalid disk name '{value}'.\n" + "The name can only contain lowercase letters, numbers and hyphens " + "with the following rules: \n" + " - the first character must be a letter; \n" + " - each hyphen must be surrounded by non-hyphen characters; \n" + f" - total length must be between {DISK_NAME_MIN_LENGTH} and " + f"{DISK_NAME_MAX_LENGTH} characters long." + ) + return value + + +DISK_NAME = JobNameType() + + class JobColumnsType(click.ParamType): name = "columns" @@ -245,3 +278,35 @@ async def async_complete( JOB = JobType() + + +class DiskType(AsyncType[str]): + name = "disk" + + async def async_convert( + self, + root: Root, + value: str, + param: Optional[click.Parameter], + ctx: Optional[click.Context], + ) -> str: + return value + + async def async_complete( + self, root: Root, ctx: click.Context, args: Sequence[str], incomplete: str + ) -> List[Tuple[str, Optional[str]]]: + async with await root.init_client() as client: + ret: List[Tuple[str, Optional[str]]] = [] + async for disk in client.disks.list(): + disk_name = disk.name or "" + for test in ( + disk.id, + disk_name, + ): + if test.startswith(incomplete): + ret.append((test, disk_name)) + + return ret + + +DISK = DiskType() diff --git a/neuro-cli/src/neuro_cli/disks.py b/neuro-cli/src/neuro_cli/disks.py index 2f4c1054a..51d3d2959 100644 --- a/neuro-cli/src/neuro_cli/disks.py +++ b/neuro-cli/src/neuro_cli/disks.py @@ -1,6 +1,9 @@ from datetime import timedelta from typing import Optional, Sequence +from neuro_cli.click_types import DISK, DISK_NAME +from neuro_cli.utils import resolve_disk + from .formatters.disks import ( BaseDisksFormatter, DiskFormatter, @@ -61,8 +64,18 @@ async def ls(root: Root, full_uri: bool, long_format: bool) -> None: "in the user config." ), ) +@option( + "--name", + type=DISK_NAME, + metavar="NAME", + help="Optional disk name", + default=None, +) async def create( - root: Root, storage: str, timeout_unused: Optional[str] = None + root: Root, + storage: str, + timeout_unused: Optional[str] = None, + name: Optional[str] = None, ) -> None: """ Create a disk with at least storage amount STORAGE. @@ -90,7 +103,7 @@ async def create( disk_timeout_unused = timedelta(seconds=timeout_unused_seconds) disk = await root.client.disks.create( - parse_memory(storage), timeout_unused=disk_timeout_unused + parse_memory(storage), timeout_unused=disk_timeout_unused, name=name ) disk_fmtr = DiskFormatter(str) with root.pager(): @@ -98,13 +111,14 @@ async def create( @command() -@argument("disk_id") +@argument("disk", type=DISK) @option("--full-uri", is_flag=True, help="Output full disk URI.") -async def get(root: Root, disk_id: str, full_uri: bool) -> None: +async def get(root: Root, disk: str, full_uri: bool) -> None: """ Get disk DISK_ID. """ - disk = await root.client.disks.get(disk_id) + disk_id = await resolve_disk(disk, client=root.client) + disk_obj = await root.client.disks.get(disk_id) if full_uri: uri_fmtr: URIFormatter = str else: @@ -113,16 +127,17 @@ async def get(root: Root, disk_id: str, full_uri: bool) -> None: ) disk_fmtr = DiskFormatter(uri_fmtr) with root.pager(): - root.print(disk_fmtr(disk)) + root.print(disk_fmtr(disk_obj)) @command() -@argument("disk_ids", nargs=-1, required=True) -async def rm(root: Root, disk_ids: Sequence[str]) -> None: +@argument("disks", type=DISK, nargs=-1, required=True) +async def rm(root: Root, disks: Sequence[str]) -> None: """ Remove disk DISK_ID. """ - for disk_id in disk_ids: + for disk in disks: + disk_id = await resolve_disk(disk, client=root.client) await root.client.disks.rm(disk_id) if root.verbosity > 0: root.print(f"Disk with id '{disk_id}' was successfully removed.") diff --git a/neuro-cli/src/neuro_cli/formatters/disks.py b/neuro-cli/src/neuro_cli/formatters/disks.py index 72148d1de..00eb30230 100644 --- a/neuro-cli/src/neuro_cli/formatters/disks.py +++ b/neuro-cli/src/neuro_cli/formatters/disks.py @@ -38,9 +38,11 @@ def __init__( def _disk_to_table_row(self, disk: Disk) -> Sequence[str]: storage_str = utils.format_size(disk.storage) + used_str = utils.format_size(disk.used_bytes) line = [ disk.id, + disk.name or "", storage_str, used_str, self._uri_formatter(disk.uri), @@ -60,6 +62,7 @@ def __call__(self, disks: Sequence[Disk]) -> RenderableType: # make sure that the first column is fully expanded width = len("disk-06bed296-8b27-4aa8-9e2a-f3c47b41c807") table.add_column("Id", style="bold", width=width) + table.add_column("Name") table.add_column("Storage") table.add_column("Used") table.add_column("Uri") @@ -89,6 +92,8 @@ def __call__(self, disk: Disk) -> RenderableType: table.add_row("Storage", utils.format_size(disk.storage)) table.add_row("Used", utils.format_size(disk.used_bytes)) table.add_row("Uri", self._uri_formatter(disk.uri)) + if disk.name: + table.add_row("Name", disk.name) table.add_row("Status", disk.status.value) table.add_row("Created at", format_datetime(disk.created_at)) table.add_row("Last used", format_datetime(disk.last_usage)) diff --git a/neuro-cli/src/neuro_cli/job.py b/neuro-cli/src/neuro_cli/job.py index 3249a5c6c..9d8bedc55 100644 --- a/neuro-cli/src/neuro_cli/job.py +++ b/neuro-cli/src/neuro_cli/job.py @@ -1,5 +1,6 @@ import asyncio import contextlib +import dataclasses import logging import shlex import sys @@ -28,6 +29,7 @@ from neuro_cli.formatters.images import DockerImageProgress from neuro_cli.formatters.utils import URIFormatter, image_formatter, uri_formatter +from neuro_cli.utils import resolve_disk from .ael import process_attach, process_exec, process_logs from .click_types import ( @@ -967,7 +969,18 @@ async def run_job( volume_parse_result = root.client.parse.volumes(volume) volumes = list(volume_parse_result.volumes) secret_files = volume_parse_result.secret_files - disk_volumes = volume_parse_result.disk_volumes + + # Replace disk names with disk ids + async def _force_disk_id(disk_uri: URL) -> URL: + disk_id = await resolve_disk(disk_uri.parts[-1], client=root.client) + return disk_uri / f"../{disk_id}" + + disk_volumes = [ + dataclasses.replace( + disk_volume, disk_uri=await _force_disk_id(disk_volume.disk_uri) + ) + for disk_volume in volume_parse_result.disk_volumes + ] if pass_config: env_name = PASS_CONFIG_ENV_NAME diff --git a/neuro-cli/src/neuro_cli/utils.py b/neuro-cli/src/neuro_cli/utils.py index 58a95152e..47d1aaf64 100644 --- a/neuro-cli/src/neuro_cli/utils.py +++ b/neuro-cli/src/neuro_cli/utils.py @@ -468,6 +468,18 @@ async def resolve_job( return id_or_name +DISK_ID_PATTERN = r"disk-[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}" + + +async def resolve_disk(id_or_name: str, *, client: Client) -> str: + # Temporary fast path. + if re.fullmatch(DISK_ID_PATTERN, id_or_name): + return id_or_name + + disk = await client.disks.get(id_or_name) + return disk.id + + SHARE_SCHEMES = ("storage", "image", "job", "blob", "role", "secret", "disk") diff --git a/neuro-cli/tests/e2e/conftest.py b/neuro-cli/tests/e2e/conftest.py index 40aee309c..beaf3c6dd 100644 --- a/neuro-cli/tests/e2e/conftest.py +++ b/neuro-cli/tests/e2e/conftest.py @@ -923,9 +923,12 @@ async def docker(loop: asyncio.AbstractEventLoop) -> AsyncIterator[aiodocker.Doc @pytest.fixture def disk_factory(helper: Helper) -> Callable[[str], ContextManager[str]]: @contextmanager - def _make_disk(storage: str) -> Iterator[str]: + def _make_disk(storage: str, name: Optional[str] = None) -> Iterator[str]: # Create disk - cap = helper.run_cli(["disk", "create", storage]) + args = ["disk", "create", storage] + if name: + args += ["--name", name] + cap = helper.run_cli(args) assert cap.err == "" disk_id = cap.out.splitlines()[0].split()[1] yield disk_id diff --git a/neuro-cli/tests/e2e/test_e2e_jobs.py b/neuro-cli/tests/e2e/test_e2e_jobs.py index 9161fa9f6..2df557ecf 100644 --- a/neuro-cli/tests/e2e/test_e2e_jobs.py +++ b/neuro-cli/tests/e2e/test_e2e_jobs.py @@ -1187,3 +1187,34 @@ def test_job_disk_volume( job_id = match.group(1) helper.wait_job_change_state_to(job_id, JobStatus.SUCCEEDED, JobStatus.FAILED) + + +@pytest.mark.e2e +def test_job_disk_volume_named( + helper: Helper, disk_factory: Callable[[str, str], ContextManager[str]] +) -> None: + disk_name = f"test-disk-{os.urandom(5).hex()}" + + with disk_factory("1G", disk_name): + bash_script = 'echo "test data" > /mnt/disk/file && cat /mnt/disk/file' + command = f"bash -c '{bash_script}'" + captured = helper.run_cli( + [ + "job", + "run", + "--life-span", + "1m", # Avoid completed job to block disk from cleanup + "-v", + f"disk:{disk_name}:/mnt/disk:rw", + "--no-wait-start", + UBUNTU_IMAGE_NAME, + command, + ] + ) + + out = captured.out + match = re.match("Job ID: (.+)", out) + assert match is not None, captured + job_id = match.group(1) + + helper.wait_job_change_state_to(job_id, JobStatus.SUCCEEDED, JobStatus.FAILED) diff --git a/neuro-cli/tests/unit/formatters/ascii/test_disk_formatter_0.ref b/neuro-cli/tests/unit/formatters/ascii/test_disk_formatter_0.ref index 8abb01ec0..4762176ae 100644 --- a/neuro-cli/tests/unit/formatters/ascii/test_disk_formatter_0.ref +++ b/neuro-cli/tests/unit/formatters/ascii/test_disk_formatter_0.ref @@ -2,6 +2,7 @@ Id disk Storage 11.9G Used Uri disk://cluster/user/disk + Name test-disk Status Ready Created at Mar 04 2017 Last used Apr 04 2017 diff --git a/neuro-cli/tests/unit/formatters/ascii/test_disks_formatter_long_0.ref b/neuro-cli/tests/unit/formatters/ascii/test_disks_formatter_long_0.ref index b83fbe38e..6e307f3ac 100644 --- a/neuro-cli/tests/unit/formatters/ascii/test_disks_formatter_long_0.ref +++ b/neuro-cli/tests/unit/formatters/ascii/test_disks_formatter_long_0.ref @@ -1,6 +1,6 @@ -Id Storage Used Uri Status Created at Last used Timeout unused - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - disk-1 50.0G disk://cluster/user/disk-1 Pending Mar 04 2017 Mar 08 2017 - disk-2 50.0M disk://cluster/user/disk-2 Ready Apr 04 2017 2d3h4m5s - disk-3 50.0K disk://cluster/user/disk-3 Ready May 04 2017 - disk-4 50B disk://cluster/user/disk-4 Broken Jun 04 2017 +Id Name Storage Used Uri Status Created at Last used Timeout unused + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + disk-1 50.0G disk://cluster/user/disk-1 Pending Mar 04 2017 Mar 08 2017 + disk-2 50.0M disk://cluster/user/disk-2 Ready Apr 04 2017 2d3h4m5s + disk-3 50.0K disk://cluster/user/disk-3 Ready May 04 2017 + disk-4 50B disk://cluster/user/disk-4 Broken Jun 04 2017 diff --git a/neuro-cli/tests/unit/formatters/ascii/test_disks_formatter_short_0.ref b/neuro-cli/tests/unit/formatters/ascii/test_disks_formatter_short_0.ref index f3855c935..4e64f8ea6 100644 --- a/neuro-cli/tests/unit/formatters/ascii/test_disks_formatter_short_0.ref +++ b/neuro-cli/tests/unit/formatters/ascii/test_disks_formatter_short_0.ref @@ -1,6 +1,6 @@ -Id Storage Used Uri Status - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - disk-1 50.0G disk://cluster/user/disk-1 Pending - disk-2 50.0M disk://cluster/user/disk-2 Ready - disk-3 50.0K disk://cluster/user/disk-3 Ready - disk-4 50B disk://cluster/user/disk-4 Broken +Id Name Storage Used Uri Status + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + disk-1 50.0G disk://cluster/user/disk-1 Pending + disk-2 50.0M disk://cluster/user/disk-2 Ready + disk-3 50.0K disk://cluster/user/disk-3 Ready + disk-4 50B disk://cluster/user/disk-4 Broken diff --git a/neuro-cli/tests/unit/formatters/test_disks.py b/neuro-cli/tests/unit/formatters/test_disks.py index 5cc3fdfa0..760427ced 100644 --- a/neuro-cli/tests/unit/formatters/test_disks.py +++ b/neuro-cli/tests/unit/formatters/test_disks.py @@ -15,14 +15,15 @@ def test_disk_formatter(rich_cmp: Any) -> None: disk = Disk( - "disk", - int(11.93 * (1024 ** 3)), - "user", - Disk.Status.READY, - "cluster", - isoparse("2017-03-04T12:28:59.759433+00:00"), - isoparse("2017-04-04T12:28:59.759433+00:00"), - timedelta(days=1, hours=2, minutes=3, seconds=4), + id="disk", + name="test-disk", + storage=int(11.93 * (1024 ** 3)), + owner="user", + status=Disk.Status.READY, + cluster_name="cluster", + created_at=isoparse("2017-03-04T12:28:59.759433+00:00"), + last_usage=isoparse("2017-04-04T12:28:59.759433+00:00"), + timeout_unused=timedelta(days=1, hours=2, minutes=3, seconds=4), ) fmtr = DiskFormatter(str) rich_cmp(fmtr(disk)) diff --git a/neuro-sdk/docs/disks_reference.rst b/neuro-sdk/docs/disks_reference.rst index 01e25fcf3..bca4972e5 100644 --- a/neuro-sdk/docs/disks_reference.rst +++ b/neuro-sdk/docs/disks_reference.rst @@ -18,7 +18,11 @@ Disks List user's disks, async iterator. Yields :class:`Disk` instances. - .. comethod:: create(storage: int, life_span: typing.Optional[datetime.timedelta]) -> Disk + .. comethod:: create( \ + storage: int, \ + life_span: typing.Optional[datetime.timedelta], \ + name: typing.Optional[str], \ + ) -> Disk Create a disk with capacity of *storage* bytes. @@ -28,21 +32,24 @@ Disks disk will be deleted. ``None`` means no limit. + :param ~typing.Optional[str] name: Name of the disk. Should be unique among all user's + disk. + :return: Newly created disk info (:class:`Disk`) - .. comethod:: get(disk_id: str) -> Disk + .. comethod:: get(disk_id_or_name: str) -> Disk - Get a disk with id *disk_id*. + Get a disk with id or name *disk_id_or_name*. - :param str disk_id: disk's id. + :param str disk_id_or_name: disk's id or name. :return: Disk info (:class:`Disk`) - .. comethod:: rm(disk_id: str) -> None + .. comethod:: rm(disk_id_or_name: str) -> None - Delete a disk with id *disk_id*. + Delete a disk with id or name *disk_id_or_name*. - :param str disk_id: disk's id. + :param str disk_id_or_name: disk's id or name. Disk @@ -70,6 +77,11 @@ Disk The disk owner username, :class:`str`. + .. attribute:: name + + The disk name set by user, unique among all user's disks, + :class:`str` or ``None`` if no name was set. + .. attribute:: status Current disk status, :class:`Disk.Status`. diff --git a/neuro-sdk/src/neuro_sdk/disks.py b/neuro-sdk/src/neuro_sdk/disks.py index 3ca303044..1f7fb7b60 100644 --- a/neuro-sdk/src/neuro_sdk/disks.py +++ b/neuro-sdk/src/neuro_sdk/disks.py @@ -23,6 +23,7 @@ class Disk: cluster_name: str created_at: datetime last_usage: Optional[datetime] = None + name: Optional[str] = None timeout_unused: Optional[timedelta] = None used_bytes: Optional[int] = None @@ -65,6 +66,7 @@ def _parse_disk_payload(self, payload: Mapping[str, Any]) -> Disk: storage=payload["storage"], used_bytes=payload.get("used_bytes"), owner=payload["owner"], + name=payload.get("name"), status=Disk.Status(payload["status"]), cluster_name=self._config.cluster_name, created_at=isoparse(payload["created_at"]), @@ -81,27 +83,31 @@ async def list(self) -> AsyncIterator[Disk]: yield self._parse_disk_payload(disk_payload) async def create( - self, storage: int, timeout_unused: Optional[timedelta] = None + self, + storage: int, + timeout_unused: Optional[timedelta] = None, + name: Optional[str] = None, ) -> Disk: url = self._config.disk_api_url auth = await self._config._api_auth() data = { "storage": storage, "life_span": timeout_unused.total_seconds() if timeout_unused else None, + "name": name, } async with self._core.request("POST", url, auth=auth, json=data) as resp: payload = await resp.json() return self._parse_disk_payload(payload) - async def get(self, disk_id: str) -> Disk: - url = self._config.disk_api_url / disk_id + async def get(self, disk_id_or_name: str) -> Disk: + url = self._config.disk_api_url / disk_id_or_name auth = await self._config._api_auth() async with self._core.request("GET", url, auth=auth) as resp: payload = await resp.json() return self._parse_disk_payload(payload) - async def rm(self, disk_id: str) -> None: - url = self._config.disk_api_url / disk_id + async def rm(self, disk_id_or_name: str) -> None: + url = self._config.disk_api_url / disk_id_or_name auth = await self._config._api_auth() async with self._core.request("DELETE", url, auth=auth): pass diff --git a/neuro-sdk/tests/test_disks.py b/neuro-sdk/tests/test_disks.py index 1e787d202..fb8e26cc9 100644 --- a/neuro-sdk/tests/test_disks.py +++ b/neuro-sdk/tests/test_disks.py @@ -27,6 +27,7 @@ async def handler(request: web.Request) -> web.Response: "owner": "user", "status": "Ready", "created_at": created_at.isoformat(), + "name": None, }, { "id": "disk-2", @@ -36,6 +37,7 @@ async def handler(request: web.Request) -> web.Response: "created_at": created_at.isoformat(), "last_usage": last_usage.isoformat(), "life_span": 3600, + "name": "test-disk", }, ] ) @@ -60,6 +62,7 @@ async def handler(request: web.Request) -> web.Response: cluster_name=cluster_config.name, created_at=created_at, timeout_unused=None, + name=None, ), Disk( id="disk-2", @@ -70,6 +73,7 @@ async def handler(request: web.Request) -> web.Response: created_at=created_at, last_usage=last_usage, timeout_unused=timedelta(hours=1), + name="test-disk", ), ] @@ -86,6 +90,7 @@ async def handler(request: web.Request) -> web.Response: assert data == { "storage": 500, "life_span": 3600, + "name": "test-disk", } return web.json_response( { @@ -95,6 +100,7 @@ async def handler(request: web.Request) -> web.Response: "status": "Ready", "created_at": created_at.isoformat(), "life_span": 3600, + "name": "test-disk", }, ) @@ -104,7 +110,7 @@ async def handler(request: web.Request) -> web.Response: srv = await aiohttp_server(app) async with make_client(srv.make_url("/")) as client: - disk = await client.disks.create(500, timedelta(hours=1)) + disk = await client.disks.create(500, timedelta(hours=1), name="test-disk") assert disk == Disk( id="disk-1", storage=500, @@ -113,6 +119,7 @@ async def handler(request: web.Request) -> web.Response: cluster_name=cluster_config.name, created_at=created_at, timeout_unused=timedelta(hours=1), + name="test-disk", )