Add node management commands
This commit is contained in:
parent
653b95ee25
commit
776daac267
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]")
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue