Add node management commands

This commit is contained in:
Joshua Boniface 2023-05-05 02:04:36 -04:00
parent 653b95ee25
commit 776daac267
5 changed files with 619 additions and 69 deletions

View File

@ -27,9 +27,14 @@ from os import environ, makedirs, path
from pkg_resources import get_distribution from pkg_resources import get_distribution
from pvc.cli.helpers import * from pvc.cli.helpers import *
from pvc.cli.waiters import *
from pvc.cli.parsers import * from pvc.cli.parsers import *
from pvc.cli.formatters import * from pvc.cli.formatters import *
import pvc.lib.cluster
import pvc.lib.node
import pvc.lib.provisioner
import click import click
@ -68,19 +73,6 @@ if not IS_COMPLETION:
############################################################################### ###############################################################################
def echo(message, newline=True, err=False):
"""
Output a message with click.echo respecting our configuration
"""
if CLI_CONFIG.get("colour", False):
colour = True
else:
colour = None
click.echo(message=message, color=colour, nl=newline, err=err)
def finish(success=True, data=None, formatter=None): def finish(success=True, data=None, formatter=None):
""" """
Output data to the terminal and exit based on code (T/F or integer code) Output data to the terminal and exit based on code (T/F or integer code)
@ -88,9 +80,9 @@ def finish(success=True, data=None, formatter=None):
if data is not None: if data is not None:
if formatter is not None: if formatter is not None:
echo(formatter(data)) echo(CLI_CONFIG, formatter(data))
else: else:
echo(data) echo(CLI_CONFIG, data)
# Allow passing # Allow passing
if isinstance(success, int): if isinstance(success, int):
@ -111,7 +103,7 @@ def version(ctx, param, value):
return return
version = get_distribution("pvc").version version = get_distribution("pvc").version
echo(f"Parallel Virtual Cluster CLI client version {version}") echo(CLI_CONFIG, f"Parallel Virtual Cluster CLI client version {version}")
ctx.exit() ctx.exit()
@ -130,30 +122,31 @@ def connection_req(function):
def validate_connection(*args, **kwargs): def validate_connection(*args, **kwargs):
if CLI_CONFIG.get("badcfg", None) and CLI_CONFIG.get("connection"): if CLI_CONFIG.get("badcfg", None) and CLI_CONFIG.get("connection"):
echo( echo(
f"""Invalid connection "{CLI_CONFIG.get('connection')}" specified; set a valid connection and try again.""" CLI_CONFIG,
f"""Invalid connection "{CLI_CONFIG.get('connection')}" specified; set a valid connection and try again.""",
) )
exit(1) exit(1)
elif CLI_CONFIG.get("badcfg", None): elif CLI_CONFIG.get("badcfg", None):
echo( echo(
'No connection specified and no local API configuration found. Use "pvc connection" to add a connection.' CLI_CONFIG,
'No connection specified and no local API configuration found. Use "pvc connection" to add a connection.',
) )
exit(1) exit(1)
if not CLI_CONFIG.get("quiet", False): if CLI_CONFIG.get("api_scheme") == "https" and not CLI_CONFIG.get("verify_ssl"):
if CLI_CONFIG.get("api_scheme") == "https" and not CLI_CONFIG.get(
"verify_ssl"
):
ssl_verify_msg = " (unverified)" ssl_verify_msg = " (unverified)"
else: else:
ssl_verify_msg = "" ssl_verify_msg = ""
echo( echo(
CLI_CONFIG,
f'''Using connection "{CLI_CONFIG.get('connection')}" - Host: "{CLI_CONFIG.get('api_host')}" Scheme: "{CLI_CONFIG.get('api_scheme')}{ssl_verify_msg}" Prefix: "{CLI_CONFIG.get('api_prefix')}"''', f'''Using connection "{CLI_CONFIG.get('connection')}" - Host: "{CLI_CONFIG.get('api_host')}" Scheme: "{CLI_CONFIG.get('api_scheme')}{ssl_verify_msg}" Prefix: "{CLI_CONFIG.get('api_prefix')}"''',
err=True, stderr=True,
) )
echo( echo(
CLI_CONFIG,
"", "",
err=True, stderr=True,
) )
return function(*args, **kwargs) return function(*args, **kwargs)
@ -195,7 +188,7 @@ def restart_opt(function):
f"Restart VM {kwargs.get('vm')}", prompt_suffix="? ", abort=True f"Restart VM {kwargs.get('vm')}", prompt_suffix="? ", abort=True
) )
except Exception: except Exception:
echo("Changes will be applied on next VM start/restart.") echo(CLI_CONFIG, "Changes will be applied on next VM start/restart.")
kwargs["restart_flag"] = False kwargs["restart_flag"] = False
return function(*args, **kwargs) return function(*args, **kwargs)
@ -251,12 +244,13 @@ def format_opt(formats, default_format="pretty"):
""" """
Click Option Decorator with argument: Click Option Decorator with argument:
Wraps a Click command that can output in multiple formats; {formats} defines a dictionary of Wraps a Click command that can output in multiple formats; {formats} defines a dictionary of
formatting functions for the command with keys as valid format types formatting functions for the command with keys as valid format types.
e.g. { "json": lambda d: json.dumps(d), "pretty": format_function_pretty, ... } e.g. { "json": lambda d: json.dumps(d), "pretty": format_function_pretty, ... }
Injects a "format_function" argument into the function for this purpose.
""" """
if default_format not in formats.keys(): if default_format not in formats.keys():
echo(f"Fatal code error: {default_format} not in {formats.keys()}") echo(CLI_CONFIG, f"Fatal code error: {default_format} not in {formats.keys()}")
exit(255) exit(255)
def format_decorator(function): def format_decorator(function):
@ -302,9 +296,9 @@ def format_opt(formats, default_format="pretty"):
) )
# Always in format {arguments}, {options}, {flags}, {format_function} # Always in format {arguments}, {options}, {flags}, {format_function}
def testing(vm, restart_flag, format_function): def testing(vm, restart_flag, format_function):
echo(vm) echo(CLI_CONFIG, vm)
echo(restart_flag) echo(CLI_CONFIG, restart_flag)
echo(format_function) echo(CLI_CONFIG, format_function)
data = { data = {
"athing": "value", "athing": "value",
@ -320,12 +314,12 @@ def testing(vm, restart_flag, format_function):
############################################################################### ###############################################################################
@click.group( @click.group(
name="cluster", name="cluster",
short_help="Manage PVC cluster.", short_help="Manage PVC clusters.",
context_settings=CONTEXT_SETTINGS, context_settings=CONTEXT_SETTINGS,
) )
def cli_cluster(): def cli_cluster():
""" """
Manage and view status of a PVC cluster. Manage and view the status of a PVC cluster.
""" """
pass pass
@ -346,7 +340,9 @@ def cli_cluster():
} }
) )
@connection_req @connection_req
def cli_cluster_status(format_function): def cli_cluster_status(
format_function,
):
""" """
Show information and health about a PVC cluster. Show information and health about a PVC cluster.
@ -379,7 +375,9 @@ def cli_cluster_status(format_function):
) )
@confirm_opt @confirm_opt
@connection_req @connection_req
def cli_cluster_init(overwrite_flag): def cli_cluster_init(
overwrite_flag,
):
""" """
Perform initialization of a new PVC cluster. Perform initialization of a new PVC cluster.
@ -392,7 +390,7 @@ def cli_cluster_init(overwrite_flag):
command. command.
""" """
echo("Some music while we're Layin' Pipe? https://youtu.be/sw8S_Kv89IU") echo(CLI_CONFIG, "Some music while we're Layin' Pipe? https://youtu.be/sw8S_Kv89IU")
retcode, retmsg = pvc.lib.cluster.initialize(CLI_CONFIG, overwrite_flag) retcode, retmsg = pvc.lib.cluster.initialize(CLI_CONFIG, overwrite_flag)
finish(retcode, retmsg) finish(retcode, retmsg)
@ -414,7 +412,9 @@ def cli_cluster_init(overwrite_flag):
help="Write backup data to this file.", help="Write backup data to this file.",
) )
@connection_req @connection_req
def cli_cluster_backup(filename): def cli_cluster_backup(
filename,
):
""" """
Create a JSON-format backup of the cluster Zookeeper state database. Create a JSON-format backup of the cluster Zookeeper state database.
""" """
@ -446,7 +446,9 @@ def cli_cluster_backup(filename):
) )
@confirm_opt @confirm_opt
@connection_req @connection_req
def cli_cluster_restore(filename): def cli_cluster_restore(
filename,
):
""" """
Restore a JSON-format backup to the cluster Zookeeper state database. Restore a JSON-format backup to the cluster Zookeeper state database.
@ -509,6 +511,408 @@ def cli_cluster_maintenance_off():
finish(retcode, retdata) finish(retcode, retdata)
###############################################################################
# pvc node
###############################################################################
@click.group(
name="node",
short_help="Manage PVC nodes.",
context_settings=CONTEXT_SETTINGS,
)
def cli_node():
"""
Manage and view the status of nodes in a PVC cluster.
"""
pass
###############################################################################
# pvc node primary
###############################################################################
@click.command(
name="primary",
short_help="Set node as primary coordinator.",
)
@click.argument("node")
@click.option(
"-w",
"--wait",
"wait_flag",
default=False,
show_default=True,
is_flag=True,
help="Block waiting for state transition",
)
@connection_req
def cli_node_primary(
node,
wait_flag,
):
"""
Set NODE in primary coordinator state, making it the primary coordinator for the cluster.
"""
# Handle active provisioner task warnings
_, tasks_retdata = pvc.lib.provisioner.task_status(CLI_CONFIG, None)
if len(tasks_retdata) > 0:
echo(
CLI_CONFIG,
f"""\
NOTE: There are currently {len(tasks_retdata)} active or queued provisioner tasks.
These jobs will continue executing, but their status visibility will be lost until
the current primary node returns to primary state.
""",
)
retcode, retdata = pvc.lib.node.node_coordinator_state(CLI_CONFIG, node, "primary")
if not retcode or "already" in retdata:
finish(retcode, retdata)
if wait_flag:
echo(CLI_CONFIG, retdata)
cli_node_waiter(CLI_CONFIG, node, "coordinator_state", "takeover")
retdata = f"Set node {node} in primary coordinator state."
finish(retcode, retdata)
###############################################################################
# pvc node secondary
###############################################################################
@click.command(
name="secondary",
short_help="Set node as secondary coordinator.",
)
@click.argument("node")
@click.option(
"-w",
"--wait",
"wait_flag",
default=False,
show_default=True,
is_flag=True,
help="Block waiting for state transition",
)
@connection_req
def cli_node_secondary(
node,
wait_flag,
):
"""
Set NODE in secondary coordinator state, making another active node the primary node for the cluster.
"""
# Handle active provisioner task warnings
_, tasks_retdata = pvc.lib.provisioner.task_status(CLI_CONFIG, None)
if len(tasks_retdata) > 0:
echo(
CLI_CONFIG,
f"""\
NOTE: There are currently {len(tasks_retdata)} active or queued provisioner tasks.
These jobs will continue executing, but their status visibility will be lost until
the current primary node returns to primary state.
""",
)
retcode, retdata = pvc.lib.node.node_coordinator_state(
CLI_CONFIG, node, "secondary"
)
if not retcode or "already" in retdata:
finish(retcode, retdata)
if wait_flag:
echo(CLI_CONFIG, retdata)
cli_node_waiter(CLI_CONFIG, node, "coordinator_state", "relinquish")
retdata = f"Set node {node} in secondary coordinator state."
finish(retcode, retdata)
###############################################################################
# pvc node flush
###############################################################################
@click.command(
name="flush",
short_help="Take node out of service.",
)
@click.argument("node")
@click.option(
"-w",
"--wait",
"wait_flag",
default=False,
show_default=True,
is_flag=True,
help="Block waiting for state transition",
)
@connection_req
def cli_node_flush(
node,
wait_flag,
):
"""
Take NODE out of service, migrating all VMs on it to other nodes.
"""
retcode, retdata = pvc.lib.node.node_domain_state(CLI_CONFIG, node, "flush")
if not retcode or "already" in retdata:
finish(retcode, retdata)
if wait_flag:
echo(CLI_CONFIG, retdata)
cli_node_waiter(CLI_CONFIG, node, "domain_state", "flush")
retdata = f"Removed node {node} from active service."
finish(retcode, retdata)
###############################################################################
# pvc node ready
###############################################################################
@click.command(
name="ready",
short_help="Restore node to service.",
)
@click.argument("node")
@click.option(
"-w",
"--wait",
"wait_flag",
default=False,
show_default=True,
is_flag=True,
help="Block waiting for state transition",
)
@connection_req
def cli_node_ready(
node,
wait_flag,
):
"""
Restore NODE to service, returning all previous VMs to it from other nodes.
"""
retcode, retdata = pvc.lib.node.node_domain_state(CLI_CONFIG, node, "ready")
if not retcode or "already" in retdata:
finish(retcode, retdata)
if wait_flag:
echo(CLI_CONFIG, retdata)
cli_node_waiter(CLI_CONFIG, node, "domain_state", "unflush")
retdata = f"Restored node {node} to active service."
finish(retcode, retdata)
###############################################################################
# pvc node log
###############################################################################
@click.command(
name="log",
short_help="View node daemon logs.",
)
@click.argument("node")
@click.option(
"-l",
"--lines",
"lines",
default=None,
show_default=False,
help="Display this many log lines from the end of the log buffer. [default: 1000; with follow: 10]",
)
@click.option(
"-f",
"--follow",
"follow_flag",
is_flag=True,
default=False,
help="Follow the live changes of the log buffer.",
)
@connection_req
def cli_node_log(
node,
lines,
follow_flag,
):
"""
Show daemon logs of NODE, either in the local $PAGER tool or following the current output.
If "-f"/"--follow" is used, log output may be delayed by up to 1-2 seconds relative to the
live system due to API refresh delays. Logs will display in batches with each API refresh.
With "--follow", the default "--lines" value is 10, otherwise it is 1000 unless "--lines" is
specified with another value.
The maximum number of lines is limited only by the systemd journal of the node, though values
above ~5000 may cause performance problems.
"""
# Set the default lines value based on the follow option
if lines is None:
if follow_flag:
lines = 10
else:
lines = 1000
if follow_flag:
# This command blocks following the logs until cancelled
retcode, retmsg = pvc.lib.node.follow_node_log(CLI_CONFIG, node, lines)
retmsg = ""
else:
retcode, retmsg = pvc.lib.node.view_node_log(CLI_CONFIG, node, lines)
click.echo_via_pager(retmsg)
retmsg = ""
finish(retcode, retmsg)
###############################################################################
# pvc node info
###############################################################################
@click.command(
name="info",
short_help="Show details of node.",
)
@click.argument("node", default=DEFAULT_NODE_HOSTNAME)
@format_opt(
{
"pretty": cli_node_info_format_pretty,
"long": cli_node_info_format_long,
"json": lambda d: jdumps(d),
"json-pretty": lambda d: jdumps(d, indent=2),
}
)
@connection_req
def cli_node_info(
node,
format_function,
):
"""
Show information about NODE. If a node is not specified, defaults to this host.
\b
Format options:
"pretty": Output basic details in a nice colourful format.
"long" Output full details including all health plugins in a nice colourful format.
"json": Output in unformatted JSON.
"json-pretty": Output in formatted JSON.
"""
retcode, retdata = pvc.lib.node.node_info(CLI_CONFIG, node)
finish(retcode, retdata, format_function)
###############################################################################
# pvc node list
###############################################################################
@click.command(
name="list",
short_help="List all nodes.",
)
@click.argument("limit", default=None, required=False)
@click.option(
"-ds",
"--daemon-state",
"daemon_state_filter",
default=None,
help="Limit list to nodes in the specified daemon state.",
)
@click.option(
"-ds",
"--coordinator-state",
"coordinator_state_filter",
default=None,
help="Limit list to nodes in the specified coordinator state.",
)
@click.option(
"-ds",
"--domain-state",
"domain_state_filter",
default=None,
help="Limit list to nodes in the specified domain state.",
)
@format_opt(
{
"pretty": cli_node_list_format_pretty,
"raw": lambda d: "\n".join([c["name"] for c in d]),
"json": lambda d: jdumps(d),
"json-pretty": lambda d: jdumps(d, indent=2),
}
)
@connection_req
def cli_node_list(
limit,
daemon_state_filter,
coordinator_state_filter,
domain_state_filter,
format_function,
):
"""
List all nodes, optionally only nodes matching regex LIMIT.
\b
Format options:
"pretty": Output all details in a nice tabular list format.
"raw": Output node names one per line.
"json": Output in unformatted JSON.
"json-pretty": Output in formatted JSON.
"""
retcode, retdata = pvc.lib.node.node_list(
CLI_CONFIG,
limit,
daemon_state_filter,
coordinator_state_filter,
domain_state_filter,
)
finish(retcode, retdata, format_function)
###############################################################################
# pvc
###############################################################################
###############################################################################
# pvc
###############################################################################
###############################################################################
# pvc
###############################################################################
###############################################################################
# pvc
###############################################################################
###############################################################################
# pvc
###############################################################################
###############################################################################
# pvc
###############################################################################
###############################################################################
# pvc
###############################################################################
###############################################################################
# pvc
###############################################################################
###############################################################################
# pvc
###############################################################################
############################################################################### ###############################################################################
# pvc connection # pvc connection
############################################################################### ###############################################################################
@ -572,7 +976,14 @@ def cli_connection():
default=False, default=False,
help="Whether or not to use SSL for the API connection. [default: False]", help="Whether or not to use SSL for the API connection. [default: False]",
) )
def cli_connection_add(name, description, address, port, api_key, ssl_flag): def cli_connection_add(
name,
description,
address,
port,
api_key,
ssl_flag,
):
""" """
Add the PVC connection NAME to the database of the local CLI client. Add the PVC connection NAME to the database of the local CLI client.
@ -611,7 +1022,9 @@ def cli_connection_add(name, description, address, port, api_key, ssl_flag):
short_help="Remove connections from the client database.", short_help="Remove connections from the client database.",
) )
@click.argument("name") @click.argument("name")
def cli_connection_remove(name): def cli_connection_remove(
name,
):
""" """
Remove the PVC connection NAME from the database of the local CLI client. Remove the PVC connection NAME from the database of the local CLI client.
""" """
@ -654,13 +1067,16 @@ def cli_connection_remove(name):
"json-pretty": lambda d: jdumps(d, indent=2), "json-pretty": lambda d: jdumps(d, indent=2),
} }
) )
def cli_connection_list(show_keys_flag, format_function): def cli_connection_list(
show_keys_flag,
format_function,
):
""" """
List all PVC connections in the database of the local CLI client. List all PVC connections in the database of the local CLI client.
\b \b
Format options: Format options:
"pretty": Output all details in a a nice tabular list format. "pretty": Output all details in a nice tabular list format.
"raw": Output connection names one per line. "raw": Output connection names one per line.
"json": Output in unformatted JSON. "json": Output in unformatted JSON.
"json-pretty": Output in formatted JSON. "json-pretty": Output in formatted JSON.
@ -685,7 +1101,9 @@ def cli_connection_list(show_keys_flag, format_function):
"json-pretty": lambda d: jdumps(d, indent=2), "json-pretty": lambda d: jdumps(d, indent=2),
} }
) )
def cli_connection_detail(format_function): def cli_connection_detail(
format_function,
):
""" """
List the status and information of all PVC cluster in the database of the local CLI client. List the status and information of all PVC cluster in the database of the local CLI client.
@ -696,11 +1114,16 @@ def cli_connection_detail(format_function):
"json-pretty": Output in formatted JSON. "json-pretty": Output in formatted JSON.
""" """
echo("Gathering information from all clusters... ", newline=False, err=True) echo(
CLI_CONFIG,
"Gathering information from all clusters... ",
newline=False,
stderr=True,
)
connections_config = get_store(store_path) connections_config = get_store(store_path)
connections_data = cli_connection_detail_parser(connections_config) connections_data = cli_connection_detail_parser(connections_config)
echo("done.", err=True) echo(CLI_CONFIG, "done.", stderr=True)
echo("", err=True) echo(CLI_CONFIG, "", stderr=True)
finish(True, connections_data, format_function) finish(True, connections_data, format_function)
@ -732,7 +1155,16 @@ def cli_connection_detail(format_function):
envvar="PVC_QUIET", envvar="PVC_QUIET",
is_flag=True, is_flag=True,
default=False, default=False,
help="Suppress connection connection information.", help="Suppress information sent to stderr.",
)
@click.option(
"-s",
"--silent",
"_silent",
envvar="PVC_SILENT",
is_flag=True,
default=False,
help="Suppress information sent to stdout and stderr.",
) )
@click.option( @click.option(
"-u", "-u",
@ -760,7 +1192,14 @@ def cli_connection_detail(format_function):
is_eager=True, is_eager=True,
help="Show CLI version and exit.", help="Show CLI version and exit.",
) )
def cli(_connection, _debug, _quiet, _unsafe, _colour): def cli(
_connection,
_debug,
_quiet,
_silent,
_unsafe,
_colour,
):
""" """
Parallel Virtual Cluster CLI management tool Parallel Virtual Cluster CLI management tool
@ -770,7 +1209,9 @@ def cli(_connection, _debug, _quiet, _unsafe, _colour):
"PVC_DEBUG": Enable additional debugging details instead of using --debug/-v "PVC_DEBUG": Enable additional debugging details instead of using --debug/-v
"PVC_QUIET": Suppress stderr connection output from client instead of using --quiet/-q "PVC_QUIET": Suppress stderr output from client instead of using --quiet/-q
"PVC_SILENT": Suppress stdout and stderr output from client instead of using --silent/-s
"PVC_UNSAFE": Always suppress confirmations instead of needing --unsafe/-u or --yes/-y; USE WITH EXTREME CARE "PVC_UNSAFE": Always suppress confirmations instead of needing --unsafe/-u or --yes/-y; USE WITH EXTREME CARE
@ -798,6 +1239,7 @@ def cli(_connection, _debug, _quiet, _unsafe, _colour):
CLI_CONFIG["unsafe"] = _unsafe CLI_CONFIG["unsafe"] = _unsafe
CLI_CONFIG["colour"] = _colour CLI_CONFIG["colour"] = _colour
CLI_CONFIG["quiet"] = _quiet CLI_CONFIG["quiet"] = _quiet
CLI_CONFIG["silent"] = _silent
audit() audit()
@ -806,6 +1248,14 @@ def cli(_connection, _debug, _quiet, _unsafe, _colour):
# Click command tree # Click command tree
############################################################################### ###############################################################################
cli_node.add_command(cli_node_primary)
cli_node.add_command(cli_node_secondary)
cli_node.add_command(cli_node_flush)
cli_node.add_command(cli_node_ready)
cli_node.add_command(cli_node_log)
cli_node.add_command(cli_node_info)
cli_node.add_command(cli_node_list)
cli.add_command(cli_node)
cli_cluster.add_command(cli_cluster_status) cli_cluster.add_command(cli_cluster_status)
cli_cluster.add_command(cli_cluster_init) cli_cluster.add_command(cli_cluster_init)
cli_cluster.add_command(cli_cluster_backup) cli_cluster.add_command(cli_cluster_backup)

View File

@ -19,7 +19,8 @@
# #
############################################################################### ###############################################################################
# import colorama from pvc.lib.node import format_info as node_format_info
from pvc.lib.node import format_list as node_format_list
# Define colour values for use in formatters # Define colour values for use in formatters
@ -422,3 +423,27 @@ def cli_connection_detail_format_pretty(data):
) )
return "\n".join(output) return "\n".join(output)
def cli_node_info_format_pretty(data):
"""
Pretty format the basic output of cli_node_info
"""
return node_format_info(data, long_output=False)
def cli_node_info_format_long(data):
"""
Pretty format the full output of cli_node_info
"""
return node_format_info(data, long_output=True)
def cli_node_list_format_pretty(data):
"""
Pretty format the output of cli_node_list
"""
return node_format_list(data)

View File

@ -19,7 +19,7 @@
# #
############################################################################### ###############################################################################
from click import echo from click import echo as click_echo
from distutils.util import strtobool from distutils.util import strtobool
from json import load as jload from json import load as jload
from json import dump as jdump from json import dump as jdump
@ -37,6 +37,24 @@ DEFAULT_API_PREFIX = "/api/v1"
DEFAULT_NODE_HOSTNAME = gethostname().split(".")[0] DEFAULT_NODE_HOSTNAME = gethostname().split(".")[0]
def echo(config, message, newline=True, stderr=False):
"""
Output a message with click.echo respecting our configuration
"""
if config.get("colour", False):
colour = True
else:
colour = None
if config.get("silent", False):
pass
elif config.get("quiet", False) and stderr:
pass
else:
click_echo(message=message, color=colour, nl=newline, err=stderr)
def audit(): def audit():
""" """
Log an audit message to the local syslog AUTH facility Log an audit message to the local syslog AUTH facility
@ -71,7 +89,6 @@ def read_config_from_yaml(cfgfile):
else None else None
) )
except KeyError: except KeyError:
echo("Invalid API YAML found, ignoring.")
host = None host = None
port = None port = None
scheme = None scheme = None

View File

@ -0,0 +1,64 @@
#!/usr/bin/env python3
# waiters.py - PVC Click CLI output waiters library
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018-2023 Joshua M. Boniface <joshua@boniface.me>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
###############################################################################
from time import sleep, time
from pvc.cli.helpers import echo
import pvc.lib.node
def cli_node_waiter(config, node, state_field, state_value):
"""
Wait for state transitions for cli_node tasks
{node} is the name of the node
{state_field} is the node_info field to check for {state_value}
{state_value} is the TRANSITIONAL value that, when no longer set, will terminate waiting
"""
# Sleep for this long between API polls
sleep_time = 1
# Print a dot after this many {sleep_time}s
dot_time = 5
t_start = time()
echo(config, "Waiting...", newline=False)
sleep(sleep_time)
count = 0
while True:
count += 1
try:
_retcode, _retdata = pvc.lib.node.node_info(config, node)
if _retdata[state_field] != state_value:
break
else:
raise ValueError
except Exception:
sleep(sleep_time)
if count % dot_time == 0:
echo(config, ".", newline=False)
t_end = time()
echo(config, f" done. [{int(t_end - t_start)}s]")

View File

@ -52,7 +52,7 @@ def node_coordinator_state(config, node, action):
return retstatus, response.json().get("message", "") return retstatus, response.json().get("message", "")
def node_domain_state(config, node, action, wait): def node_domain_state(config, node, action):
""" """
Set node domain state state (flush/ready) Set node domain state state (flush/ready)
@ -60,7 +60,7 @@ def node_domain_state(config, node, action, wait):
API arguments: action={action}, wait={wait} API arguments: action={action}, wait={wait}
API schema: {"message": "{data}"} API schema: {"message": "{data}"}
""" """
params = {"state": action, "wait": str(wait).lower()} params = {"state": action}
response = call_api( response = call_api(
config, "post", "/node/{node}/domain-state".format(node=node), params=params config, "post", "/node/{node}/domain-state".format(node=node), params=params
) )
@ -442,13 +442,7 @@ def format_info(node_information, long_output):
return "\n".join(ainformation) return "\n".join(ainformation)
def format_list(node_list, raw): def format_list(node_list):
if raw:
ainformation = list()
for node in sorted(item["name"] for item in node_list):
ainformation.append(node)
return "\n".join(ainformation)
node_list_output = [] node_list_output = []
# Determine optimal column widths # Determine optimal column widths