Skip to content

Commit

Permalink
feat(cli): Add "node show" command (#224)
Browse files Browse the repository at this point in the history
Also implement the `--node-stats` option to "group show"

Should the stat-generating function `get_stats` stay in
`client.node.stats` or should it be moved to `client.cli` (which
contains general-purpose CLI library functions)?
  • Loading branch information
ketiltrout authored Nov 13, 2024
1 parent 989be6f commit 56348b1
Show file tree
Hide file tree
Showing 6 changed files with 515 additions and 113 deletions.
47 changes: 42 additions & 5 deletions alpenhorn/cli/group/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from ...db import StorageGroup, StorageNode
from ..cli import echo
from ..node.stats import get_stats


@click.command()
Expand Down Expand Up @@ -51,19 +52,55 @@ def show(group_name, node_details, node_stats):
if nodes:
if node_details or node_stats:
if node_details:
data = [
(
details = {
node.id: (
node.name,
node.host,
"Yes" if node.active else "No",
node.io_class if node.io_class else "Default",
)
for node in nodes
}
if node_stats:
stats = get_stats(nodes, False)

# Make table
data = []
if node_stats and node_details:
headers = [
"Name",
"Host",
"Active",
"I/O Class",
"File Count",
"Total Size",
"% Full",
]
for node in nodes:
data.append(
(
*details[node.id],
stats[node.id]["count"],
stats[node.id]["size"],
stats[node.id]["percent"],
)
)
elif node_details:
headers = ["Name", "Host", "Active", "I/O Class"]
if node_stats:
# TODO: add --node-stats support when "alpenhorn node stats" is implemented
raise NotImplementedError()
for node in nodes:
data.append(details[node.id])
else:
headers = ["Name", "File Count", "Total Size", "% Full"]
for node in nodes:
data.append(
(
node.name,
stats[node.id]["count"],
stats[node.id]["size"],
stats[node.id]["percent"],
)
)

echo(tabulate(data, headers=headers))
else:
# simple list
Expand Down
2 changes: 2 additions & 0 deletions alpenhorn/cli/node/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from .list import list_
from .modify import modify
from .rename import rename
from .show import show
from .stats import stats

RE_LOCK_FILE = re.compile(r"^\..*\.lock$")
Expand All @@ -36,6 +37,7 @@ def cli():
cli.add_command(list_, "list")
cli.add_command(modify, "modify")
cli.add_command(rename, "rename")
cli.add_command(show, "show")
cli.add_command(stats, "stats")


Expand Down
98 changes: 98 additions & 0 deletions alpenhorn/cli/node/show.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""alpenhorn node show command"""

import json
import click
import peewee as pw

from ...common.util import pretty_bytes
from ...db import StorageGroup, StorageNode
from ..cli import echo
from .stats import get_stats


@click.command()
@click.argument("name", metavar="NAME")
@click.option("--stats", is_flag=True, help="Show usage stats of the node.")
def show(name, stats):
"""Show details of a Storage Node.
Shows details of the Storage Node named NODE.
"""

try:
node = StorageNode.get(name=name)
except pw.DoesNotExist:
raise click.ClickException(f"no such node: {name}")

if node.storage_type == "A":
type_name = "Archive"
elif node.storage_type == "T":
type_name = "Transport"
else:
type_name = "-"

if node.max_total_gb:
max_total = pretty_bytes(node.max_total_gb * 2**30)
else:
max_total = "-"

if node.min_avail_gb:
min_avail = pretty_bytes(node.min_avail_gb * 2**30)
else:
min_avail = "-"

if node.avail_gb:
avail = pretty_bytes(node.avail_gb * 2**30)
else:
avail = "-"

if node.avail_gb_last_checked:
last_checked = node.avail_gb_last_checked.ctime() + " UTC"
else:
last_checked = "???"

# Print a report
echo(" Storage Node: " + node.name)
echo(" Storage Group: " + node.group.name)
echo(" Active: " + ("Yes" if node.active else "No"))
echo(" Type: " + type_name)
echo(" Notes: " + (node.notes if node.notes else ""))
echo(" I/O Class: " + (node.io_class if node.io_class else "Default"))
echo()
echo(" Daemon Host: " + (node.host if node.host else ""))
echo(" Log-in Address: " + (node.address if node.address else ""))
echo("Log-in Username: " + (node.username if node.username else ""))
echo()
echo(" Auto-Import: " + ("On" if node.auto_import else "Off"))
echo(
" Auto-Verify: "
+ (f"On (Size: {node.auto_verify})" if node.auto_verify else "Off")
)
echo(" Max Total: " + max_total)
echo(" Available: " + avail)
echo(" Min Available: " + min_avail)
echo(" Last Checked: " + last_checked)

echo("\nI/O Config:\n")
if node.io_config:
try:
io_config = json.loads(node.io_config)
if io_config:
# Find length of longest key (but not too long)
keylen = min(max([len(key) for key in io_config]), 30)
for key, value in io_config.items():
echo(" " + key.rjust(keylen) + ": " + str(value))
else:
echo(" empty")
except json.JSONDecodeError:
echo("INVALID (JSON decode error)")
else:
echo(" none")

if stats:
stats = get_stats([node], False)[node.id]

echo("\nStats:\n")
echo(" Total Files: " + str(stats["count"]))
echo(" Total Size: " + stats["size"])
echo(" Usage: " + stats["percent"].lstrip() + "%")
Loading

0 comments on commit 56348b1

Please sign in to comment.