From 03d4be79b78558f07473176bd06167bdef931d68 Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Sat, 7 Nov 2020 17:35:45 -0500 Subject: [PATCH] Implement vCPU modification on the CLI Adds functions for listing and setting the vCPU and topology values from the CLI, without editing the XML directly. References #101 --- client-cli/cli_lib/vm.py | 127 +++++++++++++++++++++++++++++++++++++++ client-cli/pvc.py | 70 ++++++++++++++++++++- 2 files changed, 195 insertions(+), 2 deletions(-) diff --git a/client-cli/cli_lib/vm.py b/client-cli/cli_lib/vm.py index 7c61f692..b61c0580 100644 --- a/client-cli/cli_lib/vm.py +++ b/client-cli/cli_lib/vm.py @@ -254,6 +254,133 @@ def vm_locks(config, vm): return retstatus, response.json().get('message', '') +def vm_vcpus_set(config, vm, vcpus, topology, restart): + """ + Set the vCPU count of the VM with topology + + Calls vm_info to get the VM XML. + + Calls vm_modify to set the VM XML. + """ + from lxml.objectify import fromstring + from lxml.etree import tostring + + status, domain_information = vm_info(config, vm) + if not status: + return status, domain_information + + xml = domain_information.get('xml', None) + if xml is None: + return False, "VM does not have a valid XML doccument." + + try: + parsed_xml = fromstring(xml) + except Exception: + return False, 'ERROR: Failed to parse XML data.' + + parsed_xml.vcpu._setText(str(vcpus)) + parsed_xml.cpu.topology.set('sockets', str(topology[0])) + parsed_xml.cpu.topology.set('cores', str(topology[1])) + parsed_xml.cpu.topology.set('threads', str(topology[2])) + + try: + new_xml = tostring(parsed_xml, pretty_print=True) + except Exception: + return False, 'ERROR: Failed to dump XML data.' + + return vm_modify(config, vm, new_xml, restart) + + +def vm_vcpus_get(config, vm): + """ + Get the vCPU count of the VM + + Calls vm_info to get VM XML. + + Returns a tuple of (vcpus, (sockets, cores, threads)) + """ + from lxml.objectify import fromstring + + status, domain_information = vm_info(config, vm) + if not status: + return status, domain_information + + xml = domain_information.get('xml', None) + if xml is None: + return False, "VM does not have a valid XML doccument." + + try: + parsed_xml = fromstring(xml) + 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') + + return True, (vm_vcpus, (vm_sockets, vm_cores, vm_threads)) + + +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}{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', + threads='Threads' + ) + ) + output_list.append( + '{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[0], + sockets=vcpus[1][0], + cores=vcpus[1][1], + threads=vcpus[1][2] + ) + ) + return '\n'.join(output_list) + + def view_console_log(config, vm, lines=100): """ Return console log lines from the API (and display them in a pager in the main CLI) diff --git a/client-cli/pvc.py b/client-cli/pvc.py index 9a31d5ed..b63e22e8 100755 --- a/client-cli/pvc.py +++ b/client-cli/pvc.py @@ -1036,6 +1036,72 @@ def vm_vcpu(): pass +############################################################################### +# pvc vm vcpu get +############################################################################### +@click.command(name='get', short_help='Get the current vCPU count of a virtual machine.') +@click.argument( + 'domain' +) +@click.option( + '-r', '--raw', 'raw', is_flag=True, default=False, + help='Display the raw value only without formatting.' +) +@cluster_req +def vm_vcpu_get(domain, raw): + """ + Get the current vCPU count of the virtual machine DOMAIN. + """ + + retcode, retmsg = pvc_vm.vm_vcpus_get(config, domain) + if not raw: + retmsg = pvc_vm.format_vm_vcpus(config, domain, retmsg) + else: + retmsg = retmsg[0] # Get only the first part of the tuple (vm_vcpus) + cleanup(retcode, retmsg) + + +############################################################################### +# pvc vm vcpu set +############################################################################### +@click.command(name='set', short_help='Set the vCPU count of a virtual machine.') +@click.argument( + 'domain' +) +@click.argument( + 'vcpus' +) +@click.option( + '-t', '--topology', 'topology', default=None, + help='Use an alternative topology for the vCPUs in the CSV form ,,. SxCxT must equal VCPUS.' +) +@click.option( + '-r', '--restart', 'restart', is_flag=True, default=False, + help='Immediately restart VM to apply new config.' +) +@cluster_req +def vm_vcpu_set(domain, vcpus, topology, restart): + """ + Set the vCPU count of the virtual machine DOMAIN to VCPUS. + + By default, the topology of the vCPus is 1 socket, VCPUS cores per socket, 1 thread per core. + """ + + if topology is not None: + try: + sockets, cores, threads = topology.split(',') + if sockets * cores * threads != vcpus: + raise + except Exception: + cleanup(False, "The topology specified is not valid.") + topology = (sockets, cores, threads) + else: + topology = (1, vcpus, 1) + + retcode, retmsg = pvc_vm.vm_vcpus_set(config, domain, vcpus, topology, restart) + cleanup(retcode, retmsg) + + ############################################################################### # pvc vm memory ############################################################################### @@ -3898,8 +3964,8 @@ cli_node.add_command(node_unflush) cli_node.add_command(node_info) cli_node.add_command(node_list) -# vm_vcpu.add_command(vm_vcpu_get) -# vm_vcpu.add_command(vm_vcpu_set) +vm_vcpu.add_command(vm_vcpu_get) +vm_vcpu.add_command(vm_vcpu_set) # vm_memory.add_command(vm_memory_get) # vm_memory.add_command(vm_memory_set)