Port cluster management functions
This commit is contained in:
		| @@ -20,7 +20,9 @@ | ||||
| ############################################################################### | ||||
|  | ||||
| from functools import wraps | ||||
| from json import dump as jdump | ||||
| from json import dumps as jdumps | ||||
| from json import loads as jloads | ||||
| from os import environ, makedirs, path | ||||
| from pkg_resources import get_distribution | ||||
|  | ||||
| @@ -126,7 +128,12 @@ def connection_req(function): | ||||
|  | ||||
|     @wraps(function) | ||||
|     def validate_connection(*args, **kwargs): | ||||
|         if CLI_CONFIG.get("badcfg", None): | ||||
|         if CLI_CONFIG.get("badcfg", None) and CLI_CONFIG.get("connection"): | ||||
|             echo( | ||||
|                 f"""Invalid connection "{CLI_CONFIG.get('connection')}" specified; set a valid connection and try again.""" | ||||
|             ) | ||||
|             exit(1) | ||||
|         elif CLI_CONFIG.get("badcfg", None): | ||||
|             echo( | ||||
|                 'No connection specified and no local API configuration found. Use "pvc connection" to add a connection.' | ||||
|             ) | ||||
| @@ -142,11 +149,11 @@ def connection_req(function): | ||||
|  | ||||
|             echo( | ||||
|                 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')}"''', | ||||
|                 stderr=True, | ||||
|                 err=True, | ||||
|             ) | ||||
|             echo( | ||||
|                 "", | ||||
|                 stderr=True, | ||||
|                 err=True, | ||||
|             ) | ||||
|  | ||||
|         return function(*args, **kwargs) | ||||
| @@ -308,12 +315,206 @@ def testing(vm, restart_flag, format_function): | ||||
|     finish(True, data, format_function) | ||||
|  | ||||
|  | ||||
| ############################################################################### | ||||
| # pvc cluster | ||||
| ############################################################################### | ||||
| @click.group( | ||||
|     name="cluster", | ||||
|     short_help="Manage PVC cluster.", | ||||
|     context_settings=CONTEXT_SETTINGS, | ||||
| ) | ||||
| def cli_cluster(): | ||||
|     """ | ||||
|     Manage and view status of a PVC cluster. | ||||
|     """ | ||||
|     pass | ||||
|  | ||||
|  | ||||
| ############################################################################### | ||||
| # pvc cluster status | ||||
| ############################################################################### | ||||
| @click.command( | ||||
|     name="status", | ||||
|     short_help="Show cluster status.", | ||||
| ) | ||||
| @format_opt( | ||||
|     { | ||||
|         "pretty": cli_cluster_status_format_pretty, | ||||
|         "short": cli_cluster_status_format_short, | ||||
|         "json": lambda d: jdumps(d), | ||||
|         "json-pretty": lambda d: jdumps(d, indent=2), | ||||
|     } | ||||
| ) | ||||
| @connection_req | ||||
| def cli_cluster_status(format_function): | ||||
|     """ | ||||
|     Show information and health about a PVC cluster. | ||||
|  | ||||
|     \b | ||||
|     Format options: | ||||
|         "pretty": Output all details in a nice colourful format. | ||||
|         "short" Output only details about cluster health in a nice colourful format. | ||||
|         "json": Output in unformatted JSON. | ||||
|         "json-pretty": Output in formatted JSON. | ||||
|     """ | ||||
|  | ||||
|     retcode, retdata = pvc.lib.cluster.get_info(CLI_CONFIG) | ||||
|     finish(retcode, retdata, format_function) | ||||
|  | ||||
|  | ||||
| ############################################################################### | ||||
| # pvc cluster init | ||||
| ############################################################################### | ||||
| @click.command( | ||||
|     name="init", | ||||
|     short_help="Initialize a new cluster.", | ||||
| ) | ||||
| @click.option( | ||||
|     "-o", | ||||
|     "--overwrite", | ||||
|     "overwrite_flag", | ||||
|     is_flag=True, | ||||
|     default=False, | ||||
|     help="Remove and overwrite any existing data (DANGEROUS)", | ||||
| ) | ||||
| @confirm_opt | ||||
| @connection_req | ||||
| def cli_cluster_init(overwrite_flag): | ||||
|     """ | ||||
|     Perform initialization of a new PVC cluster. | ||||
|  | ||||
|     If the "-o"/"--overwrite" option is specified, all existing data in the cluster will be deleted | ||||
|     before new, empty data is written. THIS IS DANGEROUS. YOU WILL LOSE ALL DATA ON THE CLUSTER. Do | ||||
|     not "--overwrite" to an existing cluster unless you are absolutely sure what you are doing. | ||||
|  | ||||
|     It is not advisable to initialize a running cluster as this can cause undefined behaviour. | ||||
|     Instead, stop all node daemons first and start the API daemon manually before running this | ||||
|     command. | ||||
|     """ | ||||
|  | ||||
|     echo("Some music while we're Layin' Pipe? https://youtu.be/sw8S_Kv89IU") | ||||
|  | ||||
|     retcode, retmsg = pvc.lib.cluster.initialize(CLI_CONFIG, overwrite_flag) | ||||
|     finish(retcode, retmsg) | ||||
|  | ||||
|  | ||||
| ############################################################################### | ||||
| # pvc cluster backup | ||||
| ############################################################################### | ||||
| @click.command( | ||||
|     name="backup", | ||||
|     short_help="Create JSON backup of cluster.", | ||||
| ) | ||||
| @click.option( | ||||
|     "-f", | ||||
|     "--file", | ||||
|     "filename", | ||||
|     default=None, | ||||
|     type=click.File(mode="w"), | ||||
|     help="Write backup data to this file.", | ||||
| ) | ||||
| @connection_req | ||||
| def cli_cluster_backup(filename): | ||||
|     """ | ||||
|     Create a JSON-format backup of the cluster Zookeeper state database. | ||||
|     """ | ||||
|  | ||||
|     retcode, retdata = pvc.lib.cluster.backup(CLI_CONFIG) | ||||
|     json_data = jloads(retdata) | ||||
|     if retcode and filename is not None: | ||||
|         jdump(json_data, filename) | ||||
|         finish(retcode, f'''Backup written to file "{filename.name}"''') | ||||
|     else: | ||||
|         finish(retcode, json_data) | ||||
|  | ||||
|  | ||||
| ############################################################################### | ||||
| # pvc cluster restore | ||||
| ############################################################################### | ||||
| @click.command( | ||||
|     name="restore", | ||||
|     short_help="Restore JSON backup to cluster.", | ||||
| ) | ||||
| @click.option( | ||||
|     "-f", | ||||
|     "--filename", | ||||
|     "filename", | ||||
|     required=True, | ||||
|     default=None, | ||||
|     type=click.File(), | ||||
|     help="Read backup data from this file.", | ||||
| ) | ||||
| @confirm_opt | ||||
| @connection_req | ||||
| def cli_cluster_restore(filename): | ||||
|     """ | ||||
|     Restore a JSON-format backup to the cluster Zookeeper state database. | ||||
|  | ||||
|     All existing data in the cluster will be deleted before the restored data is written. THIS IS | ||||
|     DANGEROUS. YOU WILL LOSE ALL (CURRENT) DATA ON THE CLUSTER. Do not restore to an existing | ||||
|     cluster unless you are absolutely sure what you are doing. | ||||
|  | ||||
|     It is not advisable to restore to a running cluster as this can cause undefined behaviour. | ||||
|     Instead, stop all node daemons first and start the API daemon manually before running this | ||||
|     command. | ||||
|     """ | ||||
|  | ||||
|  | ||||
| ############################################################################### | ||||
| # pvc cluster maintenance | ||||
| ############################################################################### | ||||
| @click.group( | ||||
|     name="maintenance", | ||||
|     short_help="Manage PVC cluster maintenance state.", | ||||
|     context_settings=CONTEXT_SETTINGS, | ||||
| ) | ||||
| def cli_cluster_maintenance(): | ||||
|     """ | ||||
|     Manage the maintenance mode of a PVC cluster. | ||||
|     """ | ||||
|     pass | ||||
|  | ||||
|  | ||||
| ############################################################################### | ||||
| # pvc cluster maintenance on | ||||
| ############################################################################### | ||||
| @click.command( | ||||
|     name="on", | ||||
|     short_help="Enable cluster maintenance mode.", | ||||
| ) | ||||
| @connection_req | ||||
| def cli_cluster_maintenance_on(): | ||||
|     """ | ||||
|     Enable maintenance mode on a PVC cluster. | ||||
|     """ | ||||
|  | ||||
|     retcode, retdata = pvc.lib.cluster.maintenance_mode(CLI_CONFIG, "true") | ||||
|     finish(retcode, retdata) | ||||
|  | ||||
|  | ||||
| ############################################################################### | ||||
| # pvc cluster maintenance off | ||||
| ############################################################################### | ||||
| @click.command( | ||||
|     name="off", | ||||
|     short_help="Disable cluster maintenance mode.", | ||||
| ) | ||||
| @connection_req | ||||
| def cli_cluster_maintenance_off(): | ||||
|     """ | ||||
|     Disable maintenance mode on a PVC cluster. | ||||
|     """ | ||||
|  | ||||
|     retcode, retdata = pvc.lib.cluster.maintenance_mode(CLI_CONFIG, "false") | ||||
|     finish(retcode, retdata) | ||||
|  | ||||
|  | ||||
| ############################################################################### | ||||
| # pvc connection | ||||
| ############################################################################### | ||||
| @click.group( | ||||
|     name="connection", | ||||
|     short_help="Manage PVC cluster connections.", | ||||
|     short_help="Manage PVC API connections.", | ||||
|     context_settings=CONTEXT_SETTINGS, | ||||
| ) | ||||
| def cli_connection(): | ||||
| @@ -459,7 +660,7 @@ def cli_connection_list(show_keys_flag, format_function): | ||||
|  | ||||
|     \b | ||||
|     Format options: | ||||
|         "pretty": Output a nice tabular list of all details. | ||||
|         "pretty": Output all details in a a nice tabular list format. | ||||
|         "raw": Output connection names one per line. | ||||
|         "json": Output in unformatted JSON. | ||||
|         "json-pretty": Output in formatted JSON. | ||||
| @@ -582,11 +783,15 @@ def cli(_connection, _debug, _quiet, _unsafe, _colour): | ||||
|  | ||||
|     global CLI_CONFIG | ||||
|     store_data = get_store(store_path) | ||||
|     CLI_CONFIG = get_config(store_data, _connection) | ||||
|  | ||||
|     # There is only one connection and no local connection, so even if nothing was passed, use it | ||||
|     if len(store_data) == 1 and _connection is None and CLI_CONFIG.get("badcfg", None): | ||||
|     # If no connection is specified, use the first connection in the store | ||||
|     if _connection is None: | ||||
|         CLI_CONFIG = get_config(store_data, list(store_data.keys())[0]) | ||||
|     # If the connection isn't in the store, mark it bad but pass the value | ||||
|     elif _connection not in store_data.keys(): | ||||
|         CLI_CONFIG = {"badcfg": True, "connection": _connection} | ||||
|     else: | ||||
|         CLI_CONFIG = get_config(store_data, _connection) | ||||
|  | ||||
|     if not CLI_CONFIG.get("badcfg", None): | ||||
|         CLI_CONFIG["debug"] = _debug | ||||
| @@ -601,6 +806,14 @@ def cli(_connection, _debug, _quiet, _unsafe, _colour): | ||||
| # Click command tree | ||||
| ############################################################################### | ||||
|  | ||||
| cli_cluster.add_command(cli_cluster_status) | ||||
| cli_cluster.add_command(cli_cluster_init) | ||||
| cli_cluster.add_command(cli_cluster_backup) | ||||
| cli_cluster.add_command(cli_cluster_restore) | ||||
| cli_cluster_maintenance.add_command(cli_cluster_maintenance_on) | ||||
| cli_cluster_maintenance.add_command(cli_cluster_maintenance_off) | ||||
| cli_cluster.add_command(cli_cluster_maintenance) | ||||
| cli.add_command(cli_cluster) | ||||
| cli_connection.add_command(cli_connection_add) | ||||
| cli_connection.add_command(cli_connection_remove) | ||||
| cli_connection.add_command(cli_connection_list) | ||||
|   | ||||
| @@ -35,6 +35,203 @@ ansii = { | ||||
| } | ||||
|  | ||||
|  | ||||
| def cli_cluster_status_format_pretty(data): | ||||
|     """ | ||||
|     Pretty format the full output of cli_cluster_status | ||||
|     """ | ||||
|  | ||||
|     # Normalize data to local variables | ||||
|     health = data.get("cluster_health", {}).get("health", -1) | ||||
|     messages = data.get("cluster_health", {}).get("messages", None) | ||||
|     maintenance = data.get("maintenance", "N/A") | ||||
|     primary_node = data.get("primary_node", "N/A") | ||||
|     pvc_version = data.get("pvc_version", "N/A") | ||||
|     upstream_ip = data.get("upstream_ip", "N/A") | ||||
|     total_nodes = data.get("nodes", {}).get("total", 0) | ||||
|     total_vms = data.get("vms", {}).get("total", 0) | ||||
|     total_networks = data.get("networks", 0) | ||||
|     total_osds = data.get("osds", {}).get("total", 0) | ||||
|     total_pools = data.get("pools", 0) | ||||
|     total_volumes = data.get("volumes", 0) | ||||
|     total_snapshots = data.get("snapshots", 0) | ||||
|  | ||||
|     if maintenance == "true" or health == -1: | ||||
|         health_colour = ansii["blue"] | ||||
|     elif health > 90: | ||||
|         health_colour = ansii["green"] | ||||
|     elif health > 50: | ||||
|         health_colour = ansii["yellow"] | ||||
|     else: | ||||
|         health_colour = ansii["red"] | ||||
|  | ||||
|     output = list() | ||||
|  | ||||
|     output.append(f"{ansii['bold']}PVC cluster status:{ansii['end']}") | ||||
|     output.append("") | ||||
|  | ||||
|     if health != "-1": | ||||
|         health = f"{health}%" | ||||
|     else: | ||||
|         health = "N/A" | ||||
|  | ||||
|     if maintenance == "true": | ||||
|         health = f"{health} (maintenance on)" | ||||
|  | ||||
|     output.append( | ||||
|         f"{ansii['purple']}Cluster health:{ansii['end']}   {health_colour}{health}{ansii['end']}" | ||||
|     ) | ||||
|  | ||||
|     if messages is not None and len(messages) > 0: | ||||
|         messages = "\n                  ".join(sorted(messages)) | ||||
|         output.append(f"{ansii['purple']}Health messages:{ansii['end']}  {messages}") | ||||
|  | ||||
|     output.append("") | ||||
|  | ||||
|     output.append(f"{ansii['purple']}Primary node:{ansii['end']}     {primary_node}") | ||||
|     output.append(f"{ansii['purple']}PVC version:{ansii['end']}      {pvc_version}") | ||||
|     output.append(f"{ansii['purple']}Upstream IP:{ansii['end']}      {upstream_ip}") | ||||
|     output.append("") | ||||
|  | ||||
|     node_states = ["run,ready"] | ||||
|     node_states.extend( | ||||
|         [ | ||||
|             state | ||||
|             for state in data.get("nodes", {}).keys() | ||||
|             if state not in ["total", "run,ready"] | ||||
|         ] | ||||
|     ) | ||||
|  | ||||
|     nodes_strings = list() | ||||
|     for state in node_states: | ||||
|         if state in ["run,ready"]: | ||||
|             state_colour = ansii["green"] | ||||
|         elif state in ["run,flush", "run,unflush", "run,flushed"]: | ||||
|             state_colour = ansii["blue"] | ||||
|         elif "dead" in state or "stop" in state: | ||||
|             state_colour = ansii["red"] | ||||
|         else: | ||||
|             state_colour = ansii["yellow"] | ||||
|  | ||||
|         nodes_strings.append( | ||||
|             f"{data.get('nodes', {}).get(state)}/{total_nodes} {state_colour}{state}{ansii['end']}" | ||||
|         ) | ||||
|  | ||||
|     nodes_string = ", ".join(nodes_strings) | ||||
|  | ||||
|     output.append(f"{ansii['purple']}Nodes:{ansii['end']}            {nodes_string}") | ||||
|  | ||||
|     vm_states = ["start", "disable"] | ||||
|     vm_states.extend( | ||||
|         [ | ||||
|             state | ||||
|             for state in data.get("vms", {}).keys() | ||||
|             if state not in ["total", "start", "disable"] | ||||
|         ] | ||||
|     ) | ||||
|  | ||||
|     vms_strings = list() | ||||
|     for state in vm_states: | ||||
|         if state in ["start"]: | ||||
|             state_colour = ansii["green"] | ||||
|         elif state in ["migrate", "disable"]: | ||||
|             state_colour = ansii["blue"] | ||||
|         elif state in ["stop", "fail"]: | ||||
|             state_colour = ansii["red"] | ||||
|         else: | ||||
|             state_colour = ansii["yellow"] | ||||
|  | ||||
|         vms_strings.append( | ||||
|             f"{data.get('vms', {}).get(state)}/{total_vms} {state_colour}{state}{ansii['end']}" | ||||
|         ) | ||||
|  | ||||
|     vms_string = ", ".join(vms_strings) | ||||
|  | ||||
|     output.append(f"{ansii['purple']}VMs:{ansii['end']}              {vms_string}") | ||||
|  | ||||
|     osd_states = ["up,in"] | ||||
|     osd_states.extend( | ||||
|         [ | ||||
|             state | ||||
|             for state in data.get("osds", {}).keys() | ||||
|             if state not in ["total", "up,in"] | ||||
|         ] | ||||
|     ) | ||||
|  | ||||
|     osds_strings = list() | ||||
|     for state in osd_states: | ||||
|         if state in ["up,in"]: | ||||
|             state_colour = ansii["green"] | ||||
|         elif state in ["down,out"]: | ||||
|             state_colour = ansii["red"] | ||||
|         else: | ||||
|             state_colour = ansii["yellow"] | ||||
|  | ||||
|         osds_strings.append( | ||||
|             f"{data.get('osds', {}).get(state)}/{total_osds} {state_colour}{state}{ansii['end']}" | ||||
|         ) | ||||
|  | ||||
|     osds_string = " ".join(osds_strings) | ||||
|  | ||||
|     output.append(f"{ansii['purple']}OSDs:{ansii['end']}             {osds_string}") | ||||
|  | ||||
|     output.append(f"{ansii['purple']}Pools:{ansii['end']}            {total_pools}") | ||||
|  | ||||
|     output.append(f"{ansii['purple']}Volumes:{ansii['end']}          {total_volumes}") | ||||
|  | ||||
|     output.append(f"{ansii['purple']}Snapshots:{ansii['end']}        {total_snapshots}") | ||||
|  | ||||
|     output.append(f"{ansii['purple']}Networks:{ansii['end']}         {total_networks}") | ||||
|  | ||||
|     output.append("") | ||||
|  | ||||
|     return "\n".join(output) | ||||
|  | ||||
|  | ||||
| def cli_cluster_status_format_short(data): | ||||
|     """ | ||||
|     Pretty format the health-only output of cli_cluster_status | ||||
|     """ | ||||
|  | ||||
|     # Normalize data to local variables | ||||
|     health = data.get("cluster_health", {}).get("health", -1) | ||||
|     messages = data.get("cluster_health", {}).get("messages", None) | ||||
|     maintenance = data.get("maintenance", "N/A") | ||||
|  | ||||
|     if maintenance == "true" or health == -1: | ||||
|         health_colour = ansii["blue"] | ||||
|     elif health > 90: | ||||
|         health_colour = ansii["green"] | ||||
|     elif health > 50: | ||||
|         health_colour = ansii["yellow"] | ||||
|     else: | ||||
|         health_colour = ansii["red"] | ||||
|  | ||||
|     output = list() | ||||
|  | ||||
|     output.append(f"{ansii['bold']}PVC cluster status:{ansii['end']}") | ||||
|     output.append("") | ||||
|  | ||||
|     if health != "-1": | ||||
|         health = f"{health}%" | ||||
|     else: | ||||
|         health = "N/A" | ||||
|  | ||||
|     if maintenance == "true": | ||||
|         health = f"{health} (maintenance on)" | ||||
|  | ||||
|     output.append( | ||||
|         f"{ansii['purple']}Cluster health:{ansii['end']}   {health_colour}{health}{ansii['end']}" | ||||
|     ) | ||||
|  | ||||
|     if messages is not None and len(messages) > 0: | ||||
|         messages = "\n                  ".join(sorted(messages)) | ||||
|         output.append(f"{ansii['purple']}Health messages:{ansii['end']}  {messages}") | ||||
|  | ||||
|     output.append("") | ||||
|  | ||||
|     return "\n".join(output) | ||||
|  | ||||
|  | ||||
| def cli_connection_list_format_pretty(data): | ||||
|     """ | ||||
|     Pretty format the output of cli_connection_list | ||||
|   | ||||
| @@ -80,7 +80,7 @@ def read_config_from_yaml(cfgfile): | ||||
|     return cfgfile, host, port, scheme, api_key | ||||
|  | ||||
|  | ||||
| def get_config(store_data, cluster=None): | ||||
| def get_config(store_data, connection=None): | ||||
|     """ | ||||
|     Load CLI configuration from store data | ||||
|     """ | ||||
| @@ -88,38 +88,41 @@ def get_config(store_data, cluster=None): | ||||
|     if store_data is None: | ||||
|         return {"badcfg": True} | ||||
|  | ||||
|     cluster_details = store_data.get(cluster, None) | ||||
|     connection_details = store_data.get(connection, None) | ||||
|  | ||||
|     if not cluster_details: | ||||
|         cluster = "local" | ||||
|         cluster_details = DEFAULT_STORE_DATA | ||||
|     if not connection_details: | ||||
|         connection = "local" | ||||
|         connection_details = DEFAULT_STORE_DATA | ||||
|  | ||||
|     if cluster_details.get("cfgfile", None) is not None: | ||||
|         if path.isfile(cluster_details.get("cfgfile", None)): | ||||
|     if connection_details.get("cfgfile", None) is not None: | ||||
|         if path.isfile(connection_details.get("cfgfile", None)): | ||||
|             description, host, port, scheme, api_key = read_config_from_yaml( | ||||
|                 cluster_details.get("cfgfile", None) | ||||
|                 connection_details.get("cfgfile", None) | ||||
|             ) | ||||
|             if None in [description, host, port, scheme]: | ||||
|                 return {"badcfg": True} | ||||
|         else: | ||||
|             return {"badcfg": True} | ||||
|         # Rewrite a wildcard listener to use localhost instead | ||||
|         if host == "0.0.0.0": | ||||
|             host = "127.0.0.1" | ||||
|     else: | ||||
|         # This is a static configuration, get the details directly | ||||
|         description = cluster_details["description"] | ||||
|         host = cluster_details["host"] | ||||
|         port = cluster_details["port"] | ||||
|         scheme = cluster_details["scheme"] | ||||
|         api_key = cluster_details["api_key"] | ||||
|         description = connection_details["description"] | ||||
|         host = connection_details["host"] | ||||
|         port = connection_details["port"] | ||||
|         scheme = connection_details["scheme"] | ||||
|         api_key = connection_details["api_key"] | ||||
|  | ||||
|     config = dict() | ||||
|     config["debug"] = False | ||||
|     config["cluster"] = cluster | ||||
|     config["connection"] = connection | ||||
|     config["description"] = description | ||||
|     config["api_host"] = f"{host}:{port}" | ||||
|     config["api_scheme"] = scheme | ||||
|     config["api_key"] = api_key | ||||
|     config["api_prefix"] = DEFAULT_API_PREFIX | ||||
|     if cluster == "local": | ||||
|     if connection == "local": | ||||
|         config["verify_ssl"] = False | ||||
|     else: | ||||
|         config["verify_ssl"] = bool( | ||||
|   | ||||
| @@ -21,7 +21,6 @@ | ||||
|  | ||||
| import json | ||||
|  | ||||
| import pvc.lib.ansiprint as ansiprint | ||||
| from pvc.lib.common import call_api | ||||
|  | ||||
|  | ||||
| @@ -115,199 +114,3 @@ def get_info(config): | ||||
|         return True, response.json() | ||||
|     else: | ||||
|         return False, response.json().get("message", "") | ||||
|  | ||||
|  | ||||
| def format_info(cluster_information, oformat): | ||||
|     if oformat == "json": | ||||
|         return json.dumps(cluster_information) | ||||
|  | ||||
|     if oformat == "json-pretty": | ||||
|         return json.dumps(cluster_information, indent=4) | ||||
|  | ||||
|     # Plain formatting, i.e. human-readable | ||||
|     if ( | ||||
|         cluster_information.get("maintenance") == "true" | ||||
|         or cluster_information.get("cluster_health", {}).get("health", "N/A") == "N/A" | ||||
|     ): | ||||
|         health_colour = ansiprint.blue() | ||||
|     elif cluster_information.get("cluster_health", {}).get("health", 100) > 90: | ||||
|         health_colour = ansiprint.green() | ||||
|     elif cluster_information.get("cluster_health", {}).get("health", 100) > 50: | ||||
|         health_colour = ansiprint.yellow() | ||||
|     else: | ||||
|         health_colour = ansiprint.red() | ||||
|  | ||||
|     ainformation = [] | ||||
|  | ||||
|     ainformation.append( | ||||
|         "{}PVC cluster status:{}".format(ansiprint.bold(), ansiprint.end()) | ||||
|     ) | ||||
|     ainformation.append("") | ||||
|  | ||||
|     health_text = ( | ||||
|         f"{cluster_information.get('cluster_health', {}).get('health', 'N/A')}" | ||||
|     ) | ||||
|     if health_text != "N/A": | ||||
|         health_text += "%" | ||||
|     if cluster_information.get("maintenance") == "true": | ||||
|         health_text += " (maintenance on)" | ||||
|  | ||||
|     ainformation.append( | ||||
|         "{}Cluster health:{}  {}{}{}".format( | ||||
|             ansiprint.purple(), | ||||
|             ansiprint.end(), | ||||
|             health_colour, | ||||
|             health_text, | ||||
|             ansiprint.end(), | ||||
|         ) | ||||
|     ) | ||||
|     if cluster_information.get("cluster_health", {}).get("messages"): | ||||
|         health_messages = "\n                 > ".join( | ||||
|             sorted(cluster_information["cluster_health"]["messages"]) | ||||
|         ) | ||||
|         ainformation.append( | ||||
|             "{}Health messages:{} > {}".format( | ||||
|                 ansiprint.purple(), | ||||
|                 ansiprint.end(), | ||||
|                 health_messages, | ||||
|             ) | ||||
|         ) | ||||
|     else: | ||||
|         ainformation.append( | ||||
|             "{}Health messages:{} N/A".format( | ||||
|                 ansiprint.purple(), | ||||
|                 ansiprint.end(), | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|     if oformat == "short": | ||||
|         return "\n".join(ainformation) | ||||
|  | ||||
|     ainformation.append("") | ||||
|     ainformation.append( | ||||
|         "{}Primary node:{}        {}".format( | ||||
|             ansiprint.purple(), ansiprint.end(), cluster_information["primary_node"] | ||||
|         ) | ||||
|     ) | ||||
|     ainformation.append( | ||||
|         "{}PVC version:{}         {}".format( | ||||
|             ansiprint.purple(), | ||||
|             ansiprint.end(), | ||||
|             cluster_information.get("pvc_version", "N/A"), | ||||
|         ) | ||||
|     ) | ||||
|     ainformation.append( | ||||
|         "{}Cluster upstream IP:{} {}".format( | ||||
|             ansiprint.purple(), ansiprint.end(), cluster_information["upstream_ip"] | ||||
|         ) | ||||
|     ) | ||||
|     ainformation.append("") | ||||
|     ainformation.append( | ||||
|         "{}Total nodes:{}     {}".format( | ||||
|             ansiprint.purple(), ansiprint.end(), cluster_information["nodes"]["total"] | ||||
|         ) | ||||
|     ) | ||||
|     ainformation.append( | ||||
|         "{}Total VMs:{}       {}".format( | ||||
|             ansiprint.purple(), ansiprint.end(), cluster_information["vms"]["total"] | ||||
|         ) | ||||
|     ) | ||||
|     ainformation.append( | ||||
|         "{}Total networks:{}  {}".format( | ||||
|             ansiprint.purple(), ansiprint.end(), cluster_information["networks"] | ||||
|         ) | ||||
|     ) | ||||
|     ainformation.append( | ||||
|         "{}Total OSDs:{}      {}".format( | ||||
|             ansiprint.purple(), ansiprint.end(), cluster_information["osds"]["total"] | ||||
|         ) | ||||
|     ) | ||||
|     ainformation.append( | ||||
|         "{}Total pools:{}     {}".format( | ||||
|             ansiprint.purple(), ansiprint.end(), cluster_information["pools"] | ||||
|         ) | ||||
|     ) | ||||
|     ainformation.append( | ||||
|         "{}Total volumes:{}   {}".format( | ||||
|             ansiprint.purple(), ansiprint.end(), cluster_information["volumes"] | ||||
|         ) | ||||
|     ) | ||||
|     ainformation.append( | ||||
|         "{}Total snapshots:{} {}".format( | ||||
|             ansiprint.purple(), ansiprint.end(), cluster_information["snapshots"] | ||||
|         ) | ||||
|     ) | ||||
|  | ||||
|     nodes_string = "{}Nodes:{} {}/{} {}ready,run{}".format( | ||||
|         ansiprint.purple(), | ||||
|         ansiprint.end(), | ||||
|         cluster_information["nodes"].get("run,ready", 0), | ||||
|         cluster_information["nodes"].get("total", 0), | ||||
|         ansiprint.green(), | ||||
|         ansiprint.end(), | ||||
|     ) | ||||
|     for state, count in cluster_information["nodes"].items(): | ||||
|         if state == "total" or state == "run,ready": | ||||
|             continue | ||||
|  | ||||
|         nodes_string += " {}/{} {}{}{}".format( | ||||
|             count, | ||||
|             cluster_information["nodes"]["total"], | ||||
|             ansiprint.yellow(), | ||||
|             state, | ||||
|             ansiprint.end(), | ||||
|         ) | ||||
|  | ||||
|     ainformation.append("") | ||||
|     ainformation.append(nodes_string) | ||||
|  | ||||
|     vms_string = "{}VMs:{} {}/{} {}start{}".format( | ||||
|         ansiprint.purple(), | ||||
|         ansiprint.end(), | ||||
|         cluster_information["vms"].get("start", 0), | ||||
|         cluster_information["vms"].get("total", 0), | ||||
|         ansiprint.green(), | ||||
|         ansiprint.end(), | ||||
|     ) | ||||
|     for state, count in cluster_information["vms"].items(): | ||||
|         if state == "total" or state == "start": | ||||
|             continue | ||||
|  | ||||
|         if state in ["disable", "migrate", "unmigrate", "provision"]: | ||||
|             colour = ansiprint.blue() | ||||
|         else: | ||||
|             colour = ansiprint.yellow() | ||||
|  | ||||
|         vms_string += " {}/{} {}{}{}".format( | ||||
|             count, cluster_information["vms"]["total"], colour, state, ansiprint.end() | ||||
|         ) | ||||
|  | ||||
|     ainformation.append("") | ||||
|     ainformation.append(vms_string) | ||||
|  | ||||
|     if cluster_information["osds"]["total"] > 0: | ||||
|         osds_string = "{}Ceph OSDs:{} {}/{} {}up,in{}".format( | ||||
|             ansiprint.purple(), | ||||
|             ansiprint.end(), | ||||
|             cluster_information["osds"].get("up,in", 0), | ||||
|             cluster_information["osds"].get("total", 0), | ||||
|             ansiprint.green(), | ||||
|             ansiprint.end(), | ||||
|         ) | ||||
|         for state, count in cluster_information["osds"].items(): | ||||
|             if state == "total" or state == "up,in": | ||||
|                 continue | ||||
|  | ||||
|             osds_string += " {}/{} {}{}{}".format( | ||||
|                 count, | ||||
|                 cluster_information["osds"]["total"], | ||||
|                 ansiprint.yellow(), | ||||
|                 state, | ||||
|                 ansiprint.end(), | ||||
|             ) | ||||
|  | ||||
|         ainformation.append("") | ||||
|         ainformation.append(osds_string) | ||||
|  | ||||
|     ainformation.append("") | ||||
|     return "\n".join(ainformation) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user