Compare commits

...

3 Commits

33 changed files with 645 additions and 610 deletions

View File

@@ -3,7 +3,9 @@
# * W503 (line break before binary operator): Black moves these to new lines
# * E501 (line too long): Long lines are a fact of life in comment blocks; Black handles active instances of this
# * E203 (whitespace before ':'): Black recommends this as disabled
ignore = W503, E501
# * F403 (import * used; unable to detect undefined names): We use a wildcard for helpers
# * F405 (possibly undefined name): We use a wildcard for helpers
ignore = W503, E501, F403, F405
extend-ignore = E203
# We exclude the Debian, migrations, and provisioner examples
exclude = debian,api-daemon/migrations/versions,api-daemon/provisioner/examples,node-daemon/monitoring

View File

@@ -1,116 +0,0 @@
#!/usr/bin/env python3
# cluster.py - PVC CLI client function library, cluster management
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018-2022 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/>.
#
###############################################################################
import json
from pvc.lib.common import call_api
def initialize(config, overwrite=False):
"""
Initialize the PVC cluster
API endpoint: GET /api/v1/initialize
API arguments: overwrite, yes-i-really-mean-it
API schema: {json_data_object}
"""
params = {"yes-i-really-mean-it": "yes", "overwrite": overwrite}
response = call_api(config, "post", "/initialize", params=params)
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json().get("message", "")
def backup(config):
"""
Get a JSON backup of the cluster
API endpoint: GET /api/v1/backup
API arguments:
API schema: {json_data_object}
"""
response = call_api(config, "get", "/backup")
if response.status_code == 200:
return True, response.json()
else:
return False, response.json().get("message", "")
def restore(config, cluster_data):
"""
Restore a JSON backup to the cluster
API endpoint: POST /api/v1/restore
API arguments: yes-i-really-mean-it
API schema: {json_data_object}
"""
cluster_data_json = json.dumps(cluster_data)
params = {"yes-i-really-mean-it": "yes"}
data = {"cluster_data": cluster_data_json}
response = call_api(config, "post", "/restore", params=params, data=data)
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json().get("message", "")
def maintenance_mode(config, state):
"""
Enable or disable PVC cluster maintenance mode
API endpoint: POST /api/v1/status
API arguments: {state}={state}
API schema: {json_data_object}
"""
params = {"state": state}
response = call_api(config, "post", "/status", params=params)
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json().get("message", "")
def get_info(config):
"""
Get status of the PVC cluster
API endpoint: GET /api/v1/status
API arguments:
API schema: {json_data_object}
"""
response = call_api(config, "get", "/status")
if response.status_code == 200:
return True, response.json()
else:
return False, response.json().get("message", "")

33
client-cli-old/pvc.py Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python3
# pvc.py - PVC client command-line interface (stub testing interface)
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018-2022 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/>.
#
###############################################################################
import pvc.pvc
#
# Main entry point
#
def main():
return pvc.pvc.cli(obj={})
if __name__ == "__main__":
main()

View File

@@ -27,8 +27,8 @@ from requests_toolbelt.multipart.encoder import (
MultipartEncoderMonitor,
)
import pvc.cli_lib.ansiprint as ansiprint
from pvc.cli_lib.common import UploadProgressBar, call_api
import pvc.lib.ansiprint as ansiprint
from pvc.lib.common import UploadProgressBar, call_api
#
# Supplemental functions

View File

@@ -0,0 +1,313 @@
#!/usr/bin/env python3
# cluster.py - PVC CLI client function library, cluster management
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018-2022 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/>.
#
###############################################################################
import json
import pvc.lib.ansiprint as ansiprint
from pvc.lib.common import call_api
def initialize(config, overwrite=False):
"""
Initialize the PVC cluster
API endpoint: GET /api/v1/initialize
API arguments: overwrite, yes-i-really-mean-it
API schema: {json_data_object}
"""
params = {"yes-i-really-mean-it": "yes", "overwrite": overwrite}
response = call_api(config, "post", "/initialize", params=params)
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json().get("message", "")
def backup(config):
"""
Get a JSON backup of the cluster
API endpoint: GET /api/v1/backup
API arguments:
API schema: {json_data_object}
"""
response = call_api(config, "get", "/backup")
if response.status_code == 200:
return True, response.json()
else:
return False, response.json().get("message", "")
def restore(config, cluster_data):
"""
Restore a JSON backup to the cluster
API endpoint: POST /api/v1/restore
API arguments: yes-i-really-mean-it
API schema: {json_data_object}
"""
cluster_data_json = json.dumps(cluster_data)
params = {"yes-i-really-mean-it": "yes"}
data = {"cluster_data": cluster_data_json}
response = call_api(config, "post", "/restore", params=params, data=data)
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json().get("message", "")
def maintenance_mode(config, state):
"""
Enable or disable PVC cluster maintenance mode
API endpoint: POST /api/v1/status
API arguments: {state}={state}
API schema: {json_data_object}
"""
params = {"state": state}
response = call_api(config, "post", "/status", params=params)
if response.status_code == 200:
retstatus = True
else:
retstatus = False
return retstatus, response.json().get("message", "")
def get_info(config):
"""
Get status of the PVC cluster
API endpoint: GET /api/v1/status
API arguments:
API schema: {json_data_object}
"""
response = call_api(config, "get", "/status")
if response.status_code == 200:
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)

View File

@@ -542,16 +542,11 @@ def net_sriov_vf_info(config, node, vf):
return False, "VF not found."
else:
# Return a single instance if the response is a list
data = dict()
data["node"] = node
if isinstance(response.json(), list):
data = dict()
data["vf_information"] = response.json()[0]
return True, data
return True, response.json()[0]
# This shouldn't happen, but is here just in case
else:
data["vf_information"] = response.json()
return True, data
return True, response.json()
else:
return False, response.json().get("message", "")
@@ -719,7 +714,7 @@ def format_info(config, network_information, long_output):
)
ainformation.append("")
if retcode:
firewall_rules_string = format_list_acl(config, firewall_rules_list)
firewall_rules_string = format_list_acl(firewall_rules_list)
for line in firewall_rules_string.split("\n"):
ainformation.append(line)
else:
@@ -893,7 +888,7 @@ def format_list(config, network_list):
return "\n".join(network_list_output)
def format_list_dhcp(config, dhcp_lease_list):
def format_list_dhcp(dhcp_lease_list):
dhcp_lease_list_output = []
# Determine optimal column widths
@@ -992,7 +987,7 @@ def format_list_dhcp(config, dhcp_lease_list):
return "\n".join(dhcp_lease_list_output)
def format_list_acl(config, acl_list):
def format_list_acl(acl_list):
# Handle when we get an empty entry
if not acl_list:
acl_list = list()
@@ -1091,7 +1086,7 @@ def format_list_acl(config, acl_list):
return "\n".join(acl_list_output)
def format_list_sriov_pf(config, pf_list):
def format_list_sriov_pf(pf_list):
# The maximum column width of the VFs column
max_vfs_length = 70
@@ -1211,7 +1206,7 @@ def format_list_sriov_pf(config, pf_list):
return "\n".join(pf_list_output)
def format_list_sriov_vf(config, vf_list):
def format_list_sriov_vf(vf_list):
# Handle when we get an empty entry
if not vf_list:
vf_list = list()
@@ -1343,13 +1338,10 @@ def format_list_sriov_vf(config, vf_list):
return "\n".join(vf_list_output)
def format_info_sriov_vf(config, data):
if not data or not data["vf_information"]:
def format_info_sriov_vf(config, vf_information, node):
if not vf_information:
return "No VF found"
node = data["node"]
vf_information = data["vf_information"]
# Get information on the using VM if applicable
if vf_information["usage"]["used"] == "True" and vf_information["usage"]["domain"]:
vm_information = call_api(

View File

@@ -52,7 +52,7 @@ def node_coordinator_state(config, node, action):
return retstatus, response.json().get("message", "")
def node_domain_state(config, node, action):
def node_domain_state(config, node, action, wait):
"""
Set node domain state state (flush/ready)
@@ -60,7 +60,7 @@ def node_domain_state(config, node, action):
API arguments: action={action}, wait={wait}
API schema: {"message": "{data}"}
"""
params = {"state": action}
params = {"state": action, "wait": str(wait).lower()}
response = call_api(
config, "post", "/node/{node}/domain-state".format(node=node), params=params
)
@@ -273,7 +273,7 @@ def getOutputColours(node_information):
)
def format_info(config, node_information, long_output):
def format_info(node_information, long_output):
(
health_colour,
daemon_state_colour,
@@ -442,9 +442,12 @@ def format_info(config, node_information, long_output):
return "\n".join(ainformation)
def format_list(config, node_list):
if node_list == "Node not found.":
return node_list
def format_list(node_list, raw):
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 = []

View File

@@ -750,11 +750,24 @@ def task_status(config, task_id=None, is_watching=False):
if response.status_code == 200:
retvalue = True
respjson = response.json()
if is_watching:
# Just return the raw JSON to the watching process instead of including value
# Just return the raw JSON to the watching process instead of formatting it
return respjson
job_state = respjson["state"]
if job_state == "RUNNING":
retdata = "Job state: RUNNING\nStage: {}/{}\nStatus: {}".format(
respjson["current"], respjson["total"], respjson["status"]
)
elif job_state == "FAILED":
retdata = "Job state: FAILED\nStatus: {}".format(respjson["status"])
elif job_state == "COMPLETED":
retdata = "Job state: COMPLETED\nStatus: {}".format(respjson["status"])
else:
return retvalue, respjson
retdata = "Job state: {}\nStatus: {}".format(
respjson["state"], respjson["status"]
)
else:
retvalue = False
retdata = response.json().get("message", "")
@@ -801,7 +814,7 @@ def task_status(config, task_id=None, is_watching=False):
#
# Format functions
#
def format_list_template(config, template_data, template_type=None):
def format_list_template(template_data, template_type=None):
"""
Format the returned template template
@@ -1317,12 +1330,7 @@ def format_list_template_storage(template_template):
return "\n".join(template_list_output)
def format_list_userdata(config, userdata_data):
if not config.get("long_output"):
lines = 4
else:
lines = None
def format_list_userdata(userdata_data, lines=None):
if isinstance(userdata_data, dict):
userdata_data = [userdata_data]
@@ -1424,12 +1432,7 @@ def format_list_userdata(config, userdata_data):
return "\n".join(userdata_list_output)
def format_list_script(config, script_data):
if not config.get("long_output"):
lines = 4
else:
lines = None
def format_list_script(script_data, lines=None):
if isinstance(script_data, dict):
script_data = [script_data]
@@ -1528,7 +1531,7 @@ def format_list_script(config, script_data):
return "\n".join(script_list_output)
def format_list_ova(config, ova_data):
def format_list_ova(ova_data):
if isinstance(ova_data, dict):
ova_data = [ova_data]
@@ -1675,7 +1678,7 @@ def format_list_ova(config, ova_data):
return "\n".join(ova_list_output)
def format_list_profile(config, profile_data):
def format_list_profile(profile_data):
if isinstance(profile_data, dict):
profile_data = [profile_data]
@@ -1864,23 +1867,7 @@ def format_list_profile(config, profile_data):
return "\n".join(profile_list_output)
def format_list_task(config, task_data):
if not isinstance(task_data, list):
job_state = task_data["state"]
if job_state == "RUNNING":
retdata = "Job state: RUNNING\nStage: {}/{}\nStatus: {}".format(
task_data["current"], task_data["total"], task_data["status"]
)
elif job_state == "FAILED":
retdata = "Job state: FAILED\nStatus: {}".format(task_data["status"])
elif job_state == "COMPLETED":
retdata = "Job state: COMPLETED\nStatus: {}".format(task_data["status"])
else:
retdata = "Job state: {}\nStatus: {}".format(
task_data["state"], task_data["status"]
)
return retdata
def format_list_task(task_data):
task_list_output = []
# Determine optimal column widths

View File

@@ -286,18 +286,20 @@ def vm_tag_set(config, vm, action, tag, protected=False):
return retstatus, response.json().get("message", "")
def format_vm_tags(config, data):
def format_vm_tags(config, name, tags):
"""
Format the output of a tags dictionary in a nice table
"""
tags = data.get("tags", [])
if len(tags) < 1:
return "No tags found."
output_list = []
name_length = 5
_name_length = len(name) + 1
if _name_length > name_length:
name_length = _name_length
tags_name_length = 4
tags_type_length = 5
tags_protected_length = 10
@@ -493,38 +495,44 @@ def vm_vcpus_get(config, vm):
except Exception:
return False, "ERROR: Failed to parse XML data."
data = dict()
data["name"] = vm
data["vcpus"] = int(parsed_xml.vcpu.text)
data["sockets"] = parsed_xml.cpu.topology.attrib.get("sockets")
data["cores"] = parsed_xml.cpu.topology.attrib.get("cores")
data["threads"] = parsed_xml.cpu.topology.attrib.get("threads")
vm_vcpus = int(parsed_xml.vcpu.text)
vm_sockets = parsed_xml.cpu.topology.attrib.get("sockets")
vm_cores = parsed_xml.cpu.topology.attrib.get("cores")
vm_threads = parsed_xml.cpu.topology.attrib.get("threads")
return True, data
return True, (vm_vcpus, (vm_sockets, vm_cores, vm_threads))
def format_vm_vcpus(config, data):
def format_vm_vcpus(config, name, vcpus):
"""
Format the output of a vCPU value in a nice table
"""
output_list = []
name_length = 5
_name_length = len(name) + 1
if _name_length > name_length:
name_length = _name_length
vcpus_length = 6
sockets_length = 8
cores_length = 6
threads_length = 8
output_list.append(
"{bold}{vcpus: <{vcpus_length}} \
"{bold}{name: <{name_length}} \
{vcpus: <{vcpus_length}} \
{sockets: <{sockets_length}} \
{cores: <{cores_length}} \
{threads: <{threads_length}}{end_bold}".format(
name_length=name_length,
vcpus_length=vcpus_length,
sockets_length=sockets_length,
cores_length=cores_length,
threads_length=threads_length,
bold=ansiprint.bold(),
end_bold=ansiprint.end(),
name="Name",
vcpus="vCPUs",
sockets="Sockets",
cores="Cores",
@@ -532,20 +540,23 @@ def format_vm_vcpus(config, data):
)
)
output_list.append(
"{bold}{vcpus: <{vcpus_length}} \
"{bold}{name: <{name_length}} \
{vcpus: <{vcpus_length}} \
{sockets: <{sockets_length}} \
{cores: <{cores_length}} \
{threads: <{threads_length}}{end_bold}".format(
name_length=name_length,
vcpus_length=vcpus_length,
sockets_length=sockets_length,
cores_length=cores_length,
threads_length=threads_length,
bold="",
end_bold="",
vcpus=data["vcpus"],
sockets=data["sockets"],
cores=data["cores"],
threads=data["threads"],
name=name,
vcpus=vcpus[0],
sockets=vcpus[1][0],
cores=vcpus[1][1],
threads=vcpus[1][2],
)
)
return "\n".join(output_list)
@@ -608,35 +619,44 @@ def vm_memory_get(config, vm):
except Exception:
return False, "ERROR: Failed to parse XML data."
data = dict()
data["name"] = vm
data["memory"] = int(parsed_xml.memory.text)
vm_memory = int(parsed_xml.memory.text)
return True, data
return True, vm_memory
def format_vm_memory(config, data):
def format_vm_memory(config, name, memory):
"""
Format the output of a memory value in a nice table
"""
output_list = []
name_length = 5
_name_length = len(name) + 1
if _name_length > name_length:
name_length = _name_length
memory_length = 6
output_list.append(
"{bold}{memory: <{memory_length}}{end_bold}".format(
"{bold}{name: <{name_length}} \
{memory: <{memory_length}}{end_bold}".format(
name_length=name_length,
memory_length=memory_length,
bold=ansiprint.bold(),
end_bold=ansiprint.end(),
name="Name",
memory="RAM (M)",
)
)
output_list.append(
"{bold}{memory: <{memory_length}}{end_bold}".format(
"{bold}{name: <{name_length}} \
{memory: <{memory_length}}{end_bold}".format(
name_length=name_length,
memory_length=memory_length,
bold="",
end_bold="",
memory=data["memory"],
name=name,
memory=memory,
)
)
return "\n".join(output_list)
@@ -926,9 +946,7 @@ def vm_networks_get(config, vm):
except Exception:
return False, "ERROR: Failed to parse XML data."
data = dict()
data["name"] = vm
data["networks"] = list()
network_data = list()
for interface in parsed_xml.devices.find("interface"):
mac_address = interface.mac.attrib.get("address")
model = interface.model.attrib.get("type")
@@ -942,65 +960,76 @@ def vm_networks_get(config, vm):
elif interface_type == "hostdev":
network = "hostdev:{}".format(interface.source.attrib.get("dev"))
data["networks"].append(
{"network": network, "mac_address": mac_address, "model": model}
)
network_data.append((network, mac_address, model))
return True, data
return True, network_data
def format_vm_networks(config, data):
def format_vm_networks(config, name, networks):
"""
Format the output of a network list in a nice table
"""
output_list = []
network_length = 8
name_length = 5
vni_length = 8
macaddr_length = 12
model_length = 6
for network in data["networks"]:
_network_length = len(network["network"]) + 1
if _network_length > network_length:
network_length = _network_length
_name_length = len(name) + 1
if _name_length > name_length:
name_length = _name_length
_macaddr_length = len(network["mac_address"]) + 1
for network in networks:
_vni_length = len(network[0]) + 1
if _vni_length > vni_length:
vni_length = _vni_length
_macaddr_length = len(network[1]) + 1
if _macaddr_length > macaddr_length:
macaddr_length = _macaddr_length
_model_length = len(network["model"]) + 1
_model_length = len(network[2]) + 1
if _model_length > model_length:
model_length = _model_length
output_list.append(
"{bold}{network: <{network_length}} \
"{bold}{name: <{name_length}} \
{vni: <{vni_length}} \
{macaddr: <{macaddr_length}} \
{model: <{model_length}}{end_bold}".format(
network_length=network_length,
name_length=name_length,
vni_length=vni_length,
macaddr_length=macaddr_length,
model_length=model_length,
bold=ansiprint.bold(),
end_bold=ansiprint.end(),
network="Network",
name="Name",
vni="Network",
macaddr="MAC Address",
model="Model",
)
)
count = 0
for network in data["networks"]:
for network in networks:
if count > 0:
name = ""
count += 1
output_list.append(
"{bold}{network: <{network_length}} \
"{bold}{name: <{name_length}} \
{vni: <{vni_length}} \
{macaddr: <{macaddr_length}} \
{model: <{model_length}}{end_bold}".format(
network_length=network_length,
name_length=name_length,
vni_length=vni_length,
macaddr_length=macaddr_length,
model_length=model_length,
bold="",
end_bold="",
network=network["network"],
macaddr=network["mac_address"],
model=network["model"],
name=name,
vni=network[0],
macaddr=network[1],
model=network[2],
)
)
return "\n".join(output_list)
@@ -1241,9 +1270,7 @@ def vm_volumes_get(config, vm):
except Exception:
return False, "ERROR: Failed to parse XML data."
data = dict()
data["name"] = vm
data["volumes"] = list()
volume_data = list()
for disk in parsed_xml.devices.find("disk"):
protocol = disk.attrib.get("type")
disk_id = disk.target.attrib.get("dev")
@@ -1258,52 +1285,58 @@ def vm_volumes_get(config, vm):
protocol = "unknown"
source = "unknown"
data["volumes"].append(
{"volume": source, "disk_id": disk_id, "protocol": protocol, "bus": bus}
)
volume_data.append((source, disk_id, protocol, bus))
return True, data
return True, volume_data
def format_vm_volumes(config, data):
def format_vm_volumes(config, name, volumes):
"""
Format the output of a volume value in a nice table
"""
output_list = []
name_length = 5
volume_length = 7
disk_id_length = 4
protocol_length = 5
bus_length = 4
for volume in data["volumes"]:
_volume_length = len(volume["volume"]) + 1
_name_length = len(name) + 1
if _name_length > name_length:
name_length = _name_length
for volume in volumes:
_volume_length = len(volume[0]) + 1
if _volume_length > volume_length:
volume_length = _volume_length
_disk_id_length = len(volume["disk_id"]) + 1
_disk_id_length = len(volume[1]) + 1
if _disk_id_length > disk_id_length:
disk_id_length = _disk_id_length
_protocol_length = len(volume["protocol"]) + 1
_protocol_length = len(volume[2]) + 1
if _protocol_length > protocol_length:
protocol_length = _protocol_length
_bus_length = len(volume["bus"]) + 1
_bus_length = len(volume[3]) + 1
if _bus_length > bus_length:
bus_length = _bus_length
output_list.append(
"{bold}{volume: <{volume_length}} \
"{bold}{name: <{name_length}} \
{volume: <{volume_length}} \
{disk_id: <{disk_id_length}} \
{protocol: <{protocol_length}} \
{bus: <{bus_length}}{end_bold}".format(
name_length=name_length,
volume_length=volume_length,
disk_id_length=disk_id_length,
protocol_length=protocol_length,
bus_length=bus_length,
bold=ansiprint.bold(),
end_bold=ansiprint.end(),
name="Name",
volume="Volume",
disk_id="Dev",
protocol="Type",
@@ -1311,23 +1344,28 @@ def format_vm_volumes(config, data):
)
)
count = 0
for volume in data["volumes"]:
for volume in volumes:
if count > 0:
name = ""
count += 1
output_list.append(
"{bold}{volume: <{volume_length}} \
"{bold}{name: <{name_length}} \
{volume: <{volume_length}} \
{disk_id: <{disk_id_length}} \
{protocol: <{protocol_length}} \
{bus: <{bus_length}}{end_bold}".format(
name_length=name_length,
volume_length=volume_length,
disk_id_length=disk_id_length,
protocol_length=protocol_length,
bus_length=bus_length,
bold="",
end_bold="",
volume=volume["volume"],
disk_id=volume["disk_id"],
protocol=volume["protocol"],
bus=volume["bus"],
name=name,
volume=volume[0],
disk_id=volume[1],
protocol=volume[2],
bus=volume[3],
)
)
return "\n".join(output_list)
@@ -1831,7 +1869,7 @@ def format_info(config, domain_information, long_output):
return "\n".join(ainformation)
def format_list(config, vm_list):
def format_list(config, vm_list, raw):
# Function to strip the "br" off of nets and return a nicer list
def getNiceNetID(domain_information):
# Network list
@@ -1850,6 +1888,13 @@ def format_list(config, vm_list):
tag_list.append(tag["name"])
return tag_list
# Handle raw mode since it just lists the names
if raw:
ainformation = list()
for vm in sorted(item["name"] for item in vm_list):
ainformation.append(vm)
return "\n".join(ainformation)
vm_list_output = []
# Determine optimal column widths

View File

@@ -21,8 +21,7 @@
import json
import pvc.cli_lib.ansiprint as ansiprint
from pvc.cli_lib.common import call_api
from pvc.lib.common import call_api
def initialize(config, overwrite=False):
@@ -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)

View File

@@ -20,8 +20,8 @@
###############################################################################
import re
import pvc.cli_lib.ansiprint as ansiprint
from pvc.cli_lib.common import call_api
import pvc.lib.ansiprint as ansiprint
from pvc.lib.common import call_api
def isValidMAC(macaddr):
@@ -542,11 +542,16 @@ def net_sriov_vf_info(config, node, vf):
return False, "VF not found."
else:
# Return a single instance if the response is a list
data = dict()
data["node"] = node
if isinstance(response.json(), list):
return True, response.json()[0]
data = dict()
data["vf_information"] = response.json()[0]
return True, data
# This shouldn't happen, but is here just in case
else:
return True, response.json()
data["vf_information"] = response.json()
return True, data
else:
return False, response.json().get("message", "")
@@ -714,7 +719,7 @@ def format_info(config, network_information, long_output):
)
ainformation.append("")
if retcode:
firewall_rules_string = format_list_acl(firewall_rules_list)
firewall_rules_string = format_list_acl(config, firewall_rules_list)
for line in firewall_rules_string.split("\n"):
ainformation.append(line)
else:
@@ -888,7 +893,7 @@ def format_list(config, network_list):
return "\n".join(network_list_output)
def format_list_dhcp(dhcp_lease_list):
def format_list_dhcp(config, dhcp_lease_list):
dhcp_lease_list_output = []
# Determine optimal column widths
@@ -987,7 +992,7 @@ def format_list_dhcp(dhcp_lease_list):
return "\n".join(dhcp_lease_list_output)
def format_list_acl(acl_list):
def format_list_acl(config, acl_list):
# Handle when we get an empty entry
if not acl_list:
acl_list = list()
@@ -1086,7 +1091,7 @@ def format_list_acl(acl_list):
return "\n".join(acl_list_output)
def format_list_sriov_pf(pf_list):
def format_list_sriov_pf(config, pf_list):
# The maximum column width of the VFs column
max_vfs_length = 70
@@ -1206,7 +1211,7 @@ def format_list_sriov_pf(pf_list):
return "\n".join(pf_list_output)
def format_list_sriov_vf(vf_list):
def format_list_sriov_vf(config, vf_list):
# Handle when we get an empty entry
if not vf_list:
vf_list = list()
@@ -1338,10 +1343,13 @@ def format_list_sriov_vf(vf_list):
return "\n".join(vf_list_output)
def format_info_sriov_vf(config, vf_information, node):
if not vf_information:
def format_info_sriov_vf(config, data):
if not data or not data["vf_information"]:
return "No VF found"
node = data["node"]
vf_information = data["vf_information"]
# Get information on the using VM if applicable
if vf_information["usage"]["used"] == "True" and vf_information["usage"]["domain"]:
vm_information = call_api(

View File

@@ -21,8 +21,8 @@
import time
import pvc.cli_lib.ansiprint as ansiprint
from pvc.cli_lib.common import call_api
import pvc.lib.ansiprint as ansiprint
from pvc.lib.common import call_api
#
@@ -52,7 +52,7 @@ def node_coordinator_state(config, node, action):
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)
@@ -60,7 +60,7 @@ def node_domain_state(config, node, action, wait):
API arguments: action={action}, wait={wait}
API schema: {"message": "{data}"}
"""
params = {"state": action, "wait": str(wait).lower()}
params = {"state": action}
response = call_api(
config, "post", "/node/{node}/domain-state".format(node=node), params=params
)
@@ -273,7 +273,7 @@ def getOutputColours(node_information):
)
def format_info(node_information, long_output):
def format_info(config, node_information, long_output):
(
health_colour,
daemon_state_colour,
@@ -442,12 +442,9 @@ def format_info(node_information, long_output):
return "\n".join(ainformation)
def format_list(node_list, raw):
if raw:
ainformation = list()
for node in sorted(item["name"] for item in node_list):
ainformation.append(node)
return "\n".join(ainformation)
def format_list(config, node_list):
if node_list == "Node not found.":
return node_list
node_list_output = []

View File

@@ -24,8 +24,8 @@ from requests_toolbelt.multipart.encoder import (
MultipartEncoderMonitor,
)
import pvc.cli_lib.ansiprint as ansiprint
from pvc.cli_lib.common import UploadProgressBar, call_api
import pvc.lib.ansiprint as ansiprint
from pvc.lib.common import UploadProgressBar, call_api
from ast import literal_eval
@@ -750,24 +750,11 @@ def task_status(config, task_id=None, is_watching=False):
if response.status_code == 200:
retvalue = True
respjson = response.json()
if is_watching:
# Just return the raw JSON to the watching process instead of formatting it
# Just return the raw JSON to the watching process instead of including value
return respjson
job_state = respjson["state"]
if job_state == "RUNNING":
retdata = "Job state: RUNNING\nStage: {}/{}\nStatus: {}".format(
respjson["current"], respjson["total"], respjson["status"]
)
elif job_state == "FAILED":
retdata = "Job state: FAILED\nStatus: {}".format(respjson["status"])
elif job_state == "COMPLETED":
retdata = "Job state: COMPLETED\nStatus: {}".format(respjson["status"])
else:
retdata = "Job state: {}\nStatus: {}".format(
respjson["state"], respjson["status"]
)
return retvalue, respjson
else:
retvalue = False
retdata = response.json().get("message", "")
@@ -814,7 +801,7 @@ def task_status(config, task_id=None, is_watching=False):
#
# Format functions
#
def format_list_template(template_data, template_type=None):
def format_list_template(config, template_data, template_type=None):
"""
Format the returned template template
@@ -1330,7 +1317,12 @@ def format_list_template_storage(template_template):
return "\n".join(template_list_output)
def format_list_userdata(userdata_data, lines=None):
def format_list_userdata(config, userdata_data):
if not config.get("long_output"):
lines = 4
else:
lines = None
if isinstance(userdata_data, dict):
userdata_data = [userdata_data]
@@ -1432,7 +1424,12 @@ def format_list_userdata(userdata_data, lines=None):
return "\n".join(userdata_list_output)
def format_list_script(script_data, lines=None):
def format_list_script(config, script_data):
if not config.get("long_output"):
lines = 4
else:
lines = None
if isinstance(script_data, dict):
script_data = [script_data]
@@ -1531,7 +1528,7 @@ def format_list_script(script_data, lines=None):
return "\n".join(script_list_output)
def format_list_ova(ova_data):
def format_list_ova(config, ova_data):
if isinstance(ova_data, dict):
ova_data = [ova_data]
@@ -1678,7 +1675,7 @@ def format_list_ova(ova_data):
return "\n".join(ova_list_output)
def format_list_profile(profile_data):
def format_list_profile(config, profile_data):
if isinstance(profile_data, dict):
profile_data = [profile_data]
@@ -1867,7 +1864,23 @@ def format_list_profile(profile_data):
return "\n".join(profile_list_output)
def format_list_task(task_data):
def format_list_task(config, task_data):
if not isinstance(task_data, list):
job_state = task_data["state"]
if job_state == "RUNNING":
retdata = "Job state: RUNNING\nStage: {}/{}\nStatus: {}".format(
task_data["current"], task_data["total"], task_data["status"]
)
elif job_state == "FAILED":
retdata = "Job state: FAILED\nStatus: {}".format(task_data["status"])
elif job_state == "COMPLETED":
retdata = "Job state: COMPLETED\nStatus: {}".format(task_data["status"])
else:
retdata = "Job state: {}\nStatus: {}".format(
task_data["state"], task_data["status"]
)
return retdata
task_list_output = []
# Determine optimal column widths

View File

@@ -22,8 +22,8 @@
import time
import re
import pvc.cli_lib.ansiprint as ansiprint
from pvc.cli_lib.common import call_api, format_bytes, format_metric
import pvc.lib.ansiprint as ansiprint
from pvc.lib.common import call_api, format_bytes, format_metric
#
@@ -286,20 +286,18 @@ def vm_tag_set(config, vm, action, tag, protected=False):
return retstatus, response.json().get("message", "")
def format_vm_tags(config, name, tags):
def format_vm_tags(config, data):
"""
Format the output of a tags dictionary in a nice table
"""
tags = data.get("tags", [])
if len(tags) < 1:
return "No tags found."
output_list = []
name_length = 5
_name_length = len(name) + 1
if _name_length > name_length:
name_length = _name_length
tags_name_length = 4
tags_type_length = 5
tags_protected_length = 10
@@ -495,44 +493,38 @@ def vm_vcpus_get(config, vm):
except Exception:
return False, "ERROR: Failed to parse XML data."
vm_vcpus = int(parsed_xml.vcpu.text)
vm_sockets = parsed_xml.cpu.topology.attrib.get("sockets")
vm_cores = parsed_xml.cpu.topology.attrib.get("cores")
vm_threads = parsed_xml.cpu.topology.attrib.get("threads")
data = dict()
data["name"] = vm
data["vcpus"] = int(parsed_xml.vcpu.text)
data["sockets"] = parsed_xml.cpu.topology.attrib.get("sockets")
data["cores"] = parsed_xml.cpu.topology.attrib.get("cores")
data["threads"] = parsed_xml.cpu.topology.attrib.get("threads")
return True, (vm_vcpus, (vm_sockets, vm_cores, vm_threads))
return True, data
def format_vm_vcpus(config, name, vcpus):
def format_vm_vcpus(config, data):
"""
Format the output of a vCPU value in a nice table
"""
output_list = []
name_length = 5
_name_length = len(name) + 1
if _name_length > name_length:
name_length = _name_length
vcpus_length = 6
sockets_length = 8
cores_length = 6
threads_length = 8
output_list.append(
"{bold}{name: <{name_length}} \
{vcpus: <{vcpus_length}} \
"{bold}{vcpus: <{vcpus_length}} \
{sockets: <{sockets_length}} \
{cores: <{cores_length}} \
{threads: <{threads_length}}{end_bold}".format(
name_length=name_length,
vcpus_length=vcpus_length,
sockets_length=sockets_length,
cores_length=cores_length,
threads_length=threads_length,
bold=ansiprint.bold(),
end_bold=ansiprint.end(),
name="Name",
vcpus="vCPUs",
sockets="Sockets",
cores="Cores",
@@ -540,23 +532,20 @@ def format_vm_vcpus(config, name, vcpus):
)
)
output_list.append(
"{bold}{name: <{name_length}} \
{vcpus: <{vcpus_length}} \
"{bold}{vcpus: <{vcpus_length}} \
{sockets: <{sockets_length}} \
{cores: <{cores_length}} \
{threads: <{threads_length}}{end_bold}".format(
name_length=name_length,
vcpus_length=vcpus_length,
sockets_length=sockets_length,
cores_length=cores_length,
threads_length=threads_length,
bold="",
end_bold="",
name=name,
vcpus=vcpus[0],
sockets=vcpus[1][0],
cores=vcpus[1][1],
threads=vcpus[1][2],
vcpus=data["vcpus"],
sockets=data["sockets"],
cores=data["cores"],
threads=data["threads"],
)
)
return "\n".join(output_list)
@@ -619,44 +608,35 @@ def vm_memory_get(config, vm):
except Exception:
return False, "ERROR: Failed to parse XML data."
vm_memory = int(parsed_xml.memory.text)
data = dict()
data["name"] = vm
data["memory"] = int(parsed_xml.memory.text)
return True, vm_memory
return True, data
def format_vm_memory(config, name, memory):
def format_vm_memory(config, data):
"""
Format the output of a memory value in a nice table
"""
output_list = []
name_length = 5
_name_length = len(name) + 1
if _name_length > name_length:
name_length = _name_length
memory_length = 6
output_list.append(
"{bold}{name: <{name_length}} \
{memory: <{memory_length}}{end_bold}".format(
name_length=name_length,
"{bold}{memory: <{memory_length}}{end_bold}".format(
memory_length=memory_length,
bold=ansiprint.bold(),
end_bold=ansiprint.end(),
name="Name",
memory="RAM (M)",
)
)
output_list.append(
"{bold}{name: <{name_length}} \
{memory: <{memory_length}}{end_bold}".format(
name_length=name_length,
"{bold}{memory: <{memory_length}}{end_bold}".format(
memory_length=memory_length,
bold="",
end_bold="",
name=name,
memory=memory,
memory=data["memory"],
)
)
return "\n".join(output_list)
@@ -677,7 +657,7 @@ def vm_networks_add(
from lxml.objectify import fromstring
from lxml.etree import tostring
from random import randint
import pvc.cli_lib.network as pvc_network
import pvc.lib.network as pvc_network
network_exists, _ = pvc_network.net_info(config, network)
if not network_exists:
@@ -946,7 +926,9 @@ def vm_networks_get(config, vm):
except Exception:
return False, "ERROR: Failed to parse XML data."
network_data = list()
data = dict()
data["name"] = vm
data["networks"] = list()
for interface in parsed_xml.devices.find("interface"):
mac_address = interface.mac.attrib.get("address")
model = interface.model.attrib.get("type")
@@ -960,76 +942,65 @@ def vm_networks_get(config, vm):
elif interface_type == "hostdev":
network = "hostdev:{}".format(interface.source.attrib.get("dev"))
network_data.append((network, mac_address, model))
data["networks"].append(
{"network": network, "mac_address": mac_address, "model": model}
)
return True, network_data
return True, data
def format_vm_networks(config, name, networks):
def format_vm_networks(config, data):
"""
Format the output of a network list in a nice table
"""
output_list = []
name_length = 5
vni_length = 8
network_length = 8
macaddr_length = 12
model_length = 6
_name_length = len(name) + 1
if _name_length > name_length:
name_length = _name_length
for network in data["networks"]:
_network_length = len(network["network"]) + 1
if _network_length > network_length:
network_length = _network_length
for network in networks:
_vni_length = len(network[0]) + 1
if _vni_length > vni_length:
vni_length = _vni_length
_macaddr_length = len(network[1]) + 1
_macaddr_length = len(network["mac_address"]) + 1
if _macaddr_length > macaddr_length:
macaddr_length = _macaddr_length
_model_length = len(network[2]) + 1
_model_length = len(network["model"]) + 1
if _model_length > model_length:
model_length = _model_length
output_list.append(
"{bold}{name: <{name_length}} \
{vni: <{vni_length}} \
"{bold}{network: <{network_length}} \
{macaddr: <{macaddr_length}} \
{model: <{model_length}}{end_bold}".format(
name_length=name_length,
vni_length=vni_length,
network_length=network_length,
macaddr_length=macaddr_length,
model_length=model_length,
bold=ansiprint.bold(),
end_bold=ansiprint.end(),
name="Name",
vni="Network",
network="Network",
macaddr="MAC Address",
model="Model",
)
)
count = 0
for network in networks:
if count > 0:
name = ""
for network in data["networks"]:
count += 1
output_list.append(
"{bold}{name: <{name_length}} \
{vni: <{vni_length}} \
"{bold}{network: <{network_length}} \
{macaddr: <{macaddr_length}} \
{model: <{model_length}}{end_bold}".format(
name_length=name_length,
vni_length=vni_length,
network_length=network_length,
macaddr_length=macaddr_length,
model_length=model_length,
bold="",
end_bold="",
name=name,
vni=network[0],
macaddr=network[1],
model=network[2],
network=network["network"],
macaddr=network["mac_address"],
model=network["model"],
)
)
return "\n".join(output_list)
@@ -1046,7 +1017,7 @@ def vm_volumes_add(config, vm, volume, disk_id, bus, disk_type, live, restart):
from lxml.objectify import fromstring
from lxml.etree import tostring
from copy import deepcopy
import pvc.cli_lib.ceph as pvc_ceph
import pvc.lib.ceph as pvc_ceph
if disk_type == "rbd":
# Verify that the provided volume is valid
@@ -1270,7 +1241,9 @@ def vm_volumes_get(config, vm):
except Exception:
return False, "ERROR: Failed to parse XML data."
volume_data = list()
data = dict()
data["name"] = vm
data["volumes"] = list()
for disk in parsed_xml.devices.find("disk"):
protocol = disk.attrib.get("type")
disk_id = disk.target.attrib.get("dev")
@@ -1285,58 +1258,52 @@ def vm_volumes_get(config, vm):
protocol = "unknown"
source = "unknown"
volume_data.append((source, disk_id, protocol, bus))
data["volumes"].append(
{"volume": source, "disk_id": disk_id, "protocol": protocol, "bus": bus}
)
return True, volume_data
return True, data
def format_vm_volumes(config, name, volumes):
def format_vm_volumes(config, data):
"""
Format the output of a volume value in a nice table
"""
output_list = []
name_length = 5
volume_length = 7
disk_id_length = 4
protocol_length = 5
bus_length = 4
_name_length = len(name) + 1
if _name_length > name_length:
name_length = _name_length
for volume in volumes:
_volume_length = len(volume[0]) + 1
for volume in data["volumes"]:
_volume_length = len(volume["volume"]) + 1
if _volume_length > volume_length:
volume_length = _volume_length
_disk_id_length = len(volume[1]) + 1
_disk_id_length = len(volume["disk_id"]) + 1
if _disk_id_length > disk_id_length:
disk_id_length = _disk_id_length
_protocol_length = len(volume[2]) + 1
_protocol_length = len(volume["protocol"]) + 1
if _protocol_length > protocol_length:
protocol_length = _protocol_length
_bus_length = len(volume[3]) + 1
_bus_length = len(volume["bus"]) + 1
if _bus_length > bus_length:
bus_length = _bus_length
output_list.append(
"{bold}{name: <{name_length}} \
{volume: <{volume_length}} \
"{bold}{volume: <{volume_length}} \
{disk_id: <{disk_id_length}} \
{protocol: <{protocol_length}} \
{bus: <{bus_length}}{end_bold}".format(
name_length=name_length,
volume_length=volume_length,
disk_id_length=disk_id_length,
protocol_length=protocol_length,
bus_length=bus_length,
bold=ansiprint.bold(),
end_bold=ansiprint.end(),
name="Name",
volume="Volume",
disk_id="Dev",
protocol="Type",
@@ -1344,28 +1311,23 @@ def format_vm_volumes(config, name, volumes):
)
)
count = 0
for volume in volumes:
if count > 0:
name = ""
for volume in data["volumes"]:
count += 1
output_list.append(
"{bold}{name: <{name_length}} \
{volume: <{volume_length}} \
"{bold}{volume: <{volume_length}} \
{disk_id: <{disk_id_length}} \
{protocol: <{protocol_length}} \
{bus: <{bus_length}}{end_bold}".format(
name_length=name_length,
volume_length=volume_length,
disk_id_length=disk_id_length,
protocol_length=protocol_length,
bus_length=bus_length,
bold="",
end_bold="",
name=name,
volume=volume[0],
disk_id=volume[1],
protocol=volume[2],
bus=volume[3],
volume=volume["volume"],
disk_id=volume["disk_id"],
protocol=volume["protocol"],
bus=volume["bus"],
)
)
return "\n".join(output_list)
@@ -1869,7 +1831,7 @@ def format_info(config, domain_information, long_output):
return "\n".join(ainformation)
def format_list(config, vm_list, raw):
def format_list(config, vm_list):
# Function to strip the "br" off of nets and return a nicer list
def getNiceNetID(domain_information):
# Network list
@@ -1888,13 +1850,6 @@ def format_list(config, vm_list, raw):
tag_list.append(tag["name"])
return tag_list
# Handle raw mode since it just lists the names
if raw:
ainformation = list()
for vm in sorted(item["name"] for item in vm_list):
ainformation.append(vm)
return "\n".join(ainformation)
vm_list_output = []
# Determine optimal column widths