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

Add a neuro storage du command #2201

Merged
merged 3 commits into from
Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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 CHANGELOG.D/2201.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `neuro storage du` command that allows to retrieve cluster's storage disk usage.
21 changes: 21 additions & 0 deletions CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
* [neuro storage mkdir](#neuro-storage-mkdir)
* [neuro storage mv](#neuro-storage-mv)
* [neuro storage tree](#neuro-storage-tree)
* [neuro storage du](#neuro-storage-du)
* [neuro image](#neuro-image)
* [neuro image ls](#neuro-image-ls)
* [neuro image push](#neuro-image-push)
Expand Down Expand Up @@ -1011,6 +1012,7 @@ Name | Description|
| _[neuro storage mkdir](#neuro-storage-mkdir)_| Make directories |
| _[neuro storage mv](#neuro-storage-mv)_| Move or rename files and directories |
| _[neuro storage tree](#neuro-storage-tree)_| List contents of directories in a tree-like format |
| _[neuro storage du](#neuro-storage-du)_| Show current usage of storage |



Expand Down Expand Up @@ -1239,6 +1241,25 @@ Name | Description|



### neuro storage du

Show current usage of storage.

**Usage:**

```bash
neuro storage du [OPTIONS]
```

**Options:**

Name | Description|
|----|------------|
|_--help_|Show this message and exit.|




## neuro image

Container image operations.
Expand Down
22 changes: 22 additions & 0 deletions neuro-cli/docs/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Storage operations.
| [_mkdir_](storage.md#mkdir) | Make directories |
| [_mv_](storage.md#mv) | Move or rename files and directories |
| [_tree_](storage.md#tree) | List contents of directories in a... |
| [_du_](storage.md#du) | Show current usage of storage |


### cp
Expand Down Expand Up @@ -303,3 +304,24 @@ home dir (storage:)
| _--sort \[name | size | time\]_ | sort by given field, default is name |



### du

Show current usage of storage


#### Usage

```bash
neuro storage du [OPTIONS]
```

Show current usage of storage.

#### Options

| Name | Description |
| :--- | :--- |
| _--help_ | Show this message and exit. |


10 changes: 10 additions & 0 deletions neuro-cli/src/neuro_cli/formatters/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
StorageProgressStart,
StorageProgressStep,
)
from neuro_sdk.storage import DiskUsageInfo
from neuro_sdk.url_utils import _extract_path

from neuro_cli.root import Root
Expand Down Expand Up @@ -852,3 +853,12 @@ def _human_readable(self, size: int) -> str:

def _none(self, size: int) -> str:
return ""


class DiskUsageFormatter:
def __call__(self, usage: DiskUsageInfo) -> Text:
ret = Text.from_markup(f"Disk usage for cluster [b]{usage.cluster_name}[/]:\n")
ret.append(f"Total: {format_size(usage.total)}\n")
ret.append(f"Used: {format_size(usage.used)}\n")
ret.append(f"Free: {format_size(usage.free)}")
return ret
11 changes: 11 additions & 0 deletions neuro-cli/src/neuro_cli/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .formatters.storage import (
BaseFilesFormatter,
DeleteProgress,
DiskUsageFormatter,
FilesSorter,
LongFilesFormatter,
SimpleFilesFormatter,
Expand Down Expand Up @@ -203,6 +204,15 @@ async def glob(root: Root, patterns: Sequence[str]) -> None:
root.print(file)


@command()
async def du(root: Root) -> None:
"""
Show current usage of storage.
"""
usage = await root.client.storage.disk_usage()
root.print(DiskUsageFormatter()(usage))


class FileFilterParserOption(click.parser.Option):
def process(self, value: str, state: click.parser.ParsingState) -> None:
assert self.action == "append"
Expand Down Expand Up @@ -722,6 +732,7 @@ async def _is_dir(root: Root, uri: URL) -> bool:
storage.add_command(mkdir)
storage.add_command(mv)
storage.add_command(tree)
storage.add_command(du)


async def calc_filters(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Disk usage for cluster default:
Total: 97.7K
Used: 78.1K
Free: 19.5K
11 changes: 11 additions & 0 deletions neuro-cli/tests/unit/formatters/test_storage_formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
from yarl import URL

from neuro_sdk import Action, FileStatus, FileStatusType
from neuro_sdk.storage import DiskUsageInfo

from neuro_cli.formatters.storage import (
BaseFilesFormatter,
BSDAttributes,
BSDPainter,
DiskUsageFormatter,
FilesSorter,
GnuIndicators,
GnuPainter,
Expand Down Expand Up @@ -342,3 +344,12 @@ def test_sorter(self) -> None:
self.files[1],
self.files[2],
]


class TestUsageFormatter:
def test_formatter(self, rich_cmp: Any) -> None:
usage = DiskUsageInfo(
total=100000, used=80000, free=20000, cluster_name="default"
)
formatter = DiskUsageFormatter()
rich_cmp(formatter(usage))
33 changes: 33 additions & 0 deletions neuro-sdk/docs/storage_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ Storage

:return: data structure for given *uri*, :class:`FileStatus` object.

.. comethod:: disk_usage(cluster_name: Optional[str] = None) -> DiskUsageInfo

Return information about disk usage in given cluster.

:param str cluster_name: cluster name to retrieve info. If ``None`` current
cluster will be used.

:return: data structure for given cluster, :class:`DiskUsageInfo` object.

.. rubric:: File operations

Expand Down Expand Up @@ -526,3 +534,28 @@ StorageProgress event classes
.. attribute:: is_dir

**True** if removed item was a directory; **False** otherwise. :class:`bool`


DiskUsageInfo
=============

.. class:: DiskUsageInfo

*Read-only* :class:`~dataclasses.dataclass` for describing disk usage in particular
cluster

.. attribute:: cluster_name

Name of cluster, :class:`str`.

.. attribute:: total

Total storage size in bytes, :class:`int`.

.. attribute:: used

Used storage size in bytes, :class:`int`.

.. attribute:: free

Free storage size in bytes, :class:`int`.
33 changes: 33 additions & 0 deletions neuro-sdk/src/neuro_sdk/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ def name(self) -> str:
return Path(self.path).name


@dataclass(frozen=True)
class DiskUsageInfo:
cluster_name: str
total: int
used: int
free: int


class Storage(metaclass=NoPublicConstructor):
def __init__(self, core: _Core, config: Config) -> None:
self._core = core
Expand Down Expand Up @@ -331,6 +339,22 @@ async def stat(self, uri: URL) -> FileStatus:
res = await resp.json()
return _file_status_from_api_stat(uri.host, res["FileStatus"])

async def disk_usage(self, cluster_name: Optional[str] = None) -> DiskUsageInfo:
cluster_name = cluster_name or self._config.cluster_name
uri = self._normalize_uri(
URL(f"storage://{cluster_name}/{self._config.username}")
)
assert uri.host is not None
url = self._get_storage_url(uri, normalized=True)
url = url.with_query(op="GETDISKUSAGE")
auth = await self._config._api_auth()

request_time = time.time()
async with self._core.request("GET", url, auth=auth) as resp:
self._set_time_diff(request_time, resp)
res = await resp.json()
return _disk_usage_from_api(uri.host, res)

@asyncgeneratorcontextmanager
async def open(
self, uri: URL, offset: int = 0, size: Optional[int] = None
Expand Down Expand Up @@ -912,6 +936,15 @@ def _file_status_from_api_stat(cluster_name: str, values: Dict[str, Any]) -> Fil
)


def _disk_usage_from_api(cluster_name: str, values: Dict[str, Any]) -> DiskUsageInfo:
return DiskUsageInfo(
cluster_name=cluster_name,
total=values["total"],
used=values["used"],
free=values["free"],
)


def _parse_content_range(rng_str: Optional[str]) -> slice:
if rng_str is None:
raise RuntimeError("Missed header Content-Range")
Expand Down
42 changes: 41 additions & 1 deletion neuro-sdk/tests/test_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
StorageProgressStep,
)
from neuro_sdk.abc import StorageProgressDelete
from neuro_sdk.storage import _parse_content_range
from neuro_sdk.storage import DiskUsageInfo, _parse_content_range

from tests import _RawTestServerFactory, _TestServerFactory

Expand Down Expand Up @@ -289,6 +289,46 @@ async def handler(request: web.Request) -> web.StreamResponse:
]


async def test_storage_disk_usage(
aiohttp_server: _TestServerFactory, make_client: _MakeClient
) -> None:
async def handler(request: web.Request) -> web.StreamResponse:
assert "b3" in request.headers
assert request.path == "/storage/user"
assert request.query == {"op": "GETDISKUSAGE"}
return web.json_response({"total": 100, "used": 20, "free": 80})

app = web.Application()
app.router.add_get("/storage/user", handler)

srv = await aiohttp_server(app)

async with make_client(srv.make_url("/")) as client:
res = await client.storage.disk_usage()

assert res == DiskUsageInfo(total=100, used=20, free=80, cluster_name="default")


async def test_storage_disk_usage_another_cluster(
aiohttp_server: _TestServerFactory, make_client: _MakeClient
) -> None:
async def handler(request: web.Request) -> web.StreamResponse:
assert "b3" in request.headers
assert request.path == "/storage2/user"
assert request.query == {"op": "GETDISKUSAGE"}
return web.json_response({"total": 100, "used": 20, "free": 80})

app = web.Application()
app.router.add_get("/storage2/user", handler)

srv = await aiohttp_server(app)

async with make_client(srv.make_url("/")) as client:
res = await client.storage.disk_usage(cluster_name="another")

assert res == DiskUsageInfo(total=100, used=20, free=80, cluster_name="another")


async def test_storage_ls_another_cluster(
aiohttp_server: _TestServerFactory, make_client: _MakeClient
) -> None:
Expand Down