diff --git a/client-cli/cli_lib/vm.py b/client-cli/cli_lib/vm.py index 765a6d1e..e6f37051 100644 --- a/client-cli/cli_lib/vm.py +++ b/client-cli/cli_lib/vm.py @@ -21,6 +21,7 @@ ############################################################################### import time +import re import subprocess import click import requests @@ -45,6 +46,291 @@ def get_request_uri(config, endpoint): # # Primary functions # +def vm_info(config, vm): + """ + Get information about VM + + API endpoint: GET /api/v1/vm/{vm} + API arguments: + API schema: {json_data_object} + """ + request_uri = get_request_uri(config, '/vm/{vm}'.format(vm=vm)) + response = requests.get( + request_uri + ) + + if config['debug']: + print('API endpoint: POST {}'.format(request_uri)) + print('Response code: {}'.format(response.status_code)) + print('Response headers: {}'.format(response.headers)) + + if response.status_code == 200: + return True, response.json() + else: + return False, response.json()['message'] + +def vm_list(config, limit, target_node, target_state): + """ + Get list information about nodes (limited by {limit}, {target_node}, or {target_state}) + + API endpoint: GET /api/v1/vm + API arguments: limit={limit}, node={target_node}, state={target_state} + API schema: [{json_data_object},{json_data_object},etc.] + """ + params = dict() + if limit: + params['limit'] = limit + if target_node: + params['node'] = target_node + if target_state: + params['state'] = target_state + + request_uri = get_request_uri(config, '/vm') + response = requests.get( + request_uri, + params=params + ) + + if config['debug']: + print('API endpoint: POST {}'.format(request_uri)) + print('Response code: {}'.format(response.status_code)) + print('Response headers: {}'.format(response.headers)) + + if response.status_code == 200: + return True, response.json() + else: + return False, response.json()['message'] + +def vm_define(config, xml, node, node_limit, node_selector, node_autostart): + """ + Define a new VM on the cluster + + API endpoint: POST /vm + API arguments: xml={xml}, node={node}, limit={node_limit}, selector={node_selector}, autostart={node_autostart} + API schema: {"message":"{data}"} + """ + request_uri = get_request_uri(config, '/vm') + response = requests.post( + request_uri, + params={ + 'xml': xml, + 'node': node, + 'limit': node_limit, + 'selector': node_selector, + 'autostart': node_autostart + } + ) + + if config['debug']: + print('API endpoint: POST {}'.format(request_uri)) + print('Response code: {}'.format(response.status_code)) + print('Response headers: {}'.format(response.headers)) + + if response.status_code == 200: + retstatus = True + else: + retstatus = False + + return retstatus, response.json()['message'] + +def vm_modify(config, vm, xml, restart): + """ + Modify the configuration of VM + + API endpoint: POST /vm/{vm} + API arguments: xml={xml}, restart={restart} + API schema: {"message":"{data}"} + """ + request_uri = get_request_uri(config, '/vm/{vm}'.format(vm=vm)) + response = requests.post( + request_uri, + params={ + 'xml': xml, + 'restart': restart + } + ) + + if config['debug']: + print('API endpoint: POST {}'.format(request_uri)) + print('Response code: {}'.format(response.status_code)) + print('Response headers: {}'.format(response.headers)) + + if response.status_code == 200: + retstatus = True + else: + retstatus = False + + return retstatus, response.json()['message'] + +def vm_metadata(config, vm, node_limit, node_selector, node_autostart): + """ + Modify PVC metadata of a VM + + API endpoint: GET /vm/{vm}/meta, POST /vm/{vm}/meta + API arguments: limit={node_limit}, selector={node_selector}, autostart={node_autostart} + API schema: {"message":"{data}"} + """ + request_uri = get_request_uri(config, '/vm/{vm}/meta'.format(vm=vm)) + + # Get the existing metadata so we can perform a fully dynamic update + response = requests.get( + request_uri + ) + + if config['debug']: + print('API endpoint: GET {}'.format(request_uri)) + print('Response code: {}'.format(response.status_code)) + print('Response headers: {}'.format(response.headers)) + + metadata = response.json() + + # Update any params that we've sent + if node_limit is not None: + metadata['node_limit'] = node_limit + else: + # Collapse the existing list back down to a CSV + metadata['node_limit'] = ','.join(metadata['node_limit']) + + if node_selector is not None: + metadata['node_selector'] = node_selector + + if node_autostart is not None: + metadata['node_autostart'] = node_autostart + + # Write the new metadata + print(metadata['node_limit']) + response = requests.post( + request_uri, + params={ + 'limit': metadata['node_limit'], + 'selector': metadata['node_selector'], + 'autostart': metadata['node_autostart'] + } + ) + + if config['debug']: + print('API endpoint: POST {}'.format(request_uri)) + print('Response code: {}'.format(response.status_code)) + print('Response headers: {}'.format(response.headers)) + + if response.status_code == 200: + retstatus = True + else: + retstatus = False + + return retstatus, response.json()['message'] + +def vm_remove(config, vm, delete_disks=False): + """ + Remove a VM + + API endpoint: DELETE /vm/{vm} + API arguments: delete_disks={delete_disks} + API schema: {"message":"{data}"} + """ + request_uri = get_request_uri(config, '/vm/{vm}'.format(vm=vm)) + response = requests.delete( + request_uri, + params={ + 'delete_disks': delete_disks + } + ) + + if config['debug']: + print('API endpoint: DELETE {}'.format(request_uri)) + print('Response code: {}'.format(response.status_code)) + print('Response headers: {}'.format(response.headers)) + + if response.status_code == 200: + retstatus = True + else: + retstatus = False + + return retstatus, response.json()['message'] + +def vm_state(config, vm, target_state): + """ + Modify the current state of VM + + API endpoint: POST /vm/{vm}/state + API arguments: state={state} + API schema: {"message":"{data}"} + """ + request_uri = get_request_uri(config, '/vm/{vm}/state'.format(vm=vm)) + response = requests.post( + request_uri, + params={ + 'state': target_state, + } + ) + + if config['debug']: + print('API endpoint: POST {}'.format(request_uri)) + print('Response code: {}'.format(response.status_code)) + print('Response headers: {}'.format(response.headers)) + + if response.status_code == 200: + retstatus = True + else: + retstatus = False + + return retstatus, response.json()['message'] + +def vm_node(config, vm, target_node, action, force=False): + """ + Modify the current node of VM via {action} + + API endpoint: POST /vm/{vm}/node + API arguments: node={target_node}, action={action}, force={force} + API schema: {"message":"{data}"} + """ + request_uri = get_request_uri(config, '/vm/{vm}/node'.format(vm=vm)) + response = requests.post( + request_uri, + params={ + 'node': target_node, + 'action': action, + 'force': force + } + ) + + if config['debug']: + print('API endpoint: POST {}'.format(request_uri)) + print('Response code: {}'.format(response.status_code)) + print('Response headers: {}'.format(response.headers)) + + if response.status_code == 200: + retstatus = True + else: + retstatus = False + + return retstatus, response.json()['message'] + +def vm_locks(config, vm): + """ + Flush RBD locks of (stopped) VM + + API endpoint: POST /vm/{vm}/locks + API arguments: + API schema: {"message":"{data}"} + """ + request_uri = get_request_uri(config, '/vm/{vm}/locks'.format(vm=vm)) + response = requests.post( + request_uri + ) + + if config['debug']: + print('API endpoint: POST {}'.format(request_uri)) + print('Response code: {}'.format(response.status_code)) + print('Response headers: {}'.format(response.headers)) + + if response.status_code == 200: + retstatus = True + else: + retstatus = False + + return retstatus, response.json()['message'] + def view_console_log(config, vm, lines=100): """ Return console log lines from the API and display them in a pager @@ -54,28 +340,15 @@ def view_console_log(config, vm, lines=100): API schema: {"name":"{vmname}","data":"{console_log}"} """ request_uri = get_request_uri(config, '/vm/{vm}/console'.format(vm=vm)) - if config['debug']: - print( - 'API endpoint: GET {}'.format(request_uri) - ) - - # Get the data from the API response = requests.get( request_uri, params={'lines': lines} ) if config['debug']: - print( - 'Response code: {}'.format( - response.status_code - ) - ) - print( - 'Response headers: {}'.format( - response.headers - ) - ) + print('API endpoint: GET {}'.format(request_uri)) + print('Response code: {}'.format(response.status_code)) + print('Response headers: {}'.format(response.headers)) console_log = response.json()['data'] @@ -102,28 +375,15 @@ def follow_console_log(config, vm, lines=10): API schema: {"name":"{vmname}","data":"{console_log}"} """ request_uri = get_request_uri(config, '/vm/{vm}/console'.format(vm=vm)) - if config['debug']: - print( - 'API endpoint: GET {}'.format(request_uri) - ) - - # Get the (initial) data from the API response = requests.get( request_uri, params={'lines': lines} ) if config['debug']: - print( - 'Response code: {}'.format( - response.status_code - ) - ) - print( - 'Response headers: {}'.format( - response.headers - ) - ) + print('API endpoint: GET {}'.format(request_uri)) + print('Response code: {}'.format(response.status_code)) + print('Response headers: {}'.format(response.headers)) console_log = response.json()['data'] @@ -261,11 +521,16 @@ def format_info(config, domain_information, long_output): net_vni = net_vnis[0] else: net_vni = re.sub('br', '', net['source']) - net_exists = zkhandler.exists(zk_conn, '/networks/{}'.format(net_vni)) - if not net_exists and net_vni != 'cluster': + + request_uri = get_request_uri(config, '/network/{net}'.format(net=net_vni)) + response = requests.get( + request_uri + ) + if response.status_code != 200 and net_vni != 'cluster': net_list.append(ansiprint.red() + net_vni + ansiprint.end() + ' [invalid]') else: net_list.append(net_vni) + ainformation.append('') ainformation.append('{}Networks:{} {}'.format(ansiprint.purple(), ansiprint.end(), ', '.join(net_list))) @@ -382,7 +647,9 @@ def format_list(config, vm_list, raw): vm_migrated='Migrated' ) ) - + + # Keep track of nets we found to be valid to cut down on duplicate API hits + valid_net_list = [] # Format the string (elements) for domain_information in vm_list: if domain_information['state'] == 'start': @@ -403,9 +670,16 @@ def format_list(config, vm_list, raw): net_list = [] vm_net_colour = '' for net_vni in raw_net_list: - net_exists = zkhandler.exists(zk_conn, '/networks/{}'.format(net_vni)) - if not net_exists and net_vni != 'cluster': - vm_net_colour = ansiprint.red() + if not net_vni in valid_net_list: + request_uri = get_request_uri(config, '/network/{net}'.format(net=net_vni)) + response = requests.get( + request_uri + ) + if response.status_code != 200 and net_vni != 'cluster': + vm_net_colour = ansiprint.red() + else: + valid_net_list.append(net_vni) + net_list.append(net_vni) vm_list_output.append( @@ -442,4 +716,3 @@ def format_list(config, vm_list, raw): click.echo('\n'.join(sorted(vm_list_output))) return True, '' - diff --git a/client-cli/pvc.py b/client-cli/pvc.py index a576c5d0..d4ed2f43 100755 --- a/client-cli/pvc.py +++ b/client-cli/pvc.py @@ -29,6 +29,7 @@ import difflib import re import colorama import yaml +import lxml.etree as etree import requests import cli_lib.ansiprint as ansiprint @@ -232,16 +233,12 @@ def vm_define(config, target_node, node_limit, node_selector, node_autostart): Define a new virtual machine from Libvirt XML configuration file CONFIG. """ - if node_limit: - node_limit = node_limit.split(',') - # Open the XML file config_data = config.read() config.close() - zk_conn = pvc_common.startZKConnection(zk_host) retcode, retmsg = pvc_vm.define_vm(zk_conn, config_data, target_node, node_limit, node_selector, node_autostart) - cleanup(retcode, retmsg, zk_conn) + cleanup(retcode, retmsg) ############################################################################### # pvc vm meta @@ -271,12 +268,8 @@ def vm_meta(domain, node_limit, node_selector, node_autostart): if node_limit is None and node_selector is None and node_autostart is None: cleanup(False, 'At least one metadata option must be specified to update.') - if node_limit: - node_limit = node_limit.split(',') - - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.modify_vm_metadata(zk_conn, domain, node_limit, node_selector, node_autostart) - cleanup(retcode, retmsg, zk_conn) + retcode, retmsg = pvc_vm.vm_metadata(config, domain, node_limit, node_selector, node_autostart) + cleanup(retcode, retmsg) ############################################################################### # pvc vm modify @@ -294,31 +287,33 @@ def vm_meta(domain, node_limit, node_selector, node_autostart): 'domain' ) @click.argument( - 'config', type=click.File(), default=None, required=False + 'cfgfile', type=click.File(), default=None, required=False ) -def vm_modify(domain, config, editor, restart): +def vm_modify(domain, cfgfile, editor, restart): """ Modify existing virtual machine DOMAIN, either in-editor or with replacement CONFIG. DOMAIN may be a UUID or name. """ - if editor == False and config == None: + if editor == False and cfgfile == None: cleanup(False, 'Either an XML config file or the "--editor" option must be specified.') - zk_conn = pvc_common.startZKConnection(zk_host) - - dom_uuid = pvc_vm.getDomainUUID(zk_conn, domain) - if dom_uuid == None: + retcode, vm_information = pvc_vm.vm_info(config, domain) + if not retcode and not vm_information.get('name', None): cleanup(False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)) - dom_name = pvc_vm.getDomainName(zk_conn, dom_uuid) + + dom_uuid = vm_information.get('uuid') + dom_name = vm_information.get('name') if editor == True: # Grab the current config - current_vm_config = zk_conn.get('/domains/{}/xml'.format(dom_uuid))[0].decode('ascii') + current_vm_cfg_raw = vm_information.get('xml') + xml_data = etree.fromstring(current_vm_cfg_raw) + current_vm_cfgfile = etree.tostring(xml_data, pretty_print=True).decode('utf8') # Write it to a tempfile fd, path = tempfile.mkstemp() fw = os.fdopen(fd, 'w') - fw.write(current_vm_config) + fw.write(current_vm_cfgfile.strip()) fw.close() # Edit it @@ -327,14 +322,14 @@ def vm_modify(domain, config, editor, restart): # Open the tempfile to read with open(path, 'r') as fr: - new_vm_config = fr.read() + new_vm_cfgfile = fr.read() fr.close() # Delete the tempfile os.unlink(path) # Show a diff and confirm - diff = list(difflib.unified_diff(current_vm_config.split('\n'), new_vm_config.split('\n'), fromfile='current', tofile='modified', fromfiledate='', tofiledate='', n=3, lineterm='')) + diff = list(difflib.unified_diff(current_vm_cfgfile.split('\n'), new_vm_cfgfile.split('\n'), fromfile='current', tofile='modified', fromfiledate='', tofiledate='', n=3, lineterm='')) if len(diff) < 1: click.echo('Aborting with no modifications.') exit(0) @@ -355,23 +350,23 @@ def vm_modify(domain, config, editor, restart): click.confirm('Write modifications to Zookeeper?', abort=True) if restart: - click.echo('Writing modified config of VM "{}" and restarting.'.format(dom_name)) + click.echo('Writing modified configuration of VM "{}" and restarting.'.format(dom_name)) else: - click.echo('Writing modified config of VM "{}".'.format(dom_name)) + click.echo('Writing modified configuration of VM "{}".'.format(dom_name)) # We're operating in replace mode else: # Open the XML file - new_vm_config = config.read() - config.close() + new_vm_cfgfile = cfgfile.read() + cfgfile.close() if restart: - click.echo('Replacing config of VM "{}" with file "{}" and restarting.'.format(dom_name, config.name)) + click.echo('Replacing configuration of VM "{}" with file "{}" and restarting.'.format(dom_name, cfgfile.name)) else: - click.echo('Replacing config of VM "{}" with file "{}".'.format(dom_name, config.name)) + click.echo('Replacing configuration of VM "{}" with file "{}".'.format(dom_name, cfgfile.name)) - retcode, retmsg = pvc_vm.modify_vm(zk_conn, domain, restart, new_vm_config) - cleanup(retcode, retmsg, zk_conn) + retcode, retmsg = pvc_vm.vm_modify(config, domain, new_vm_config, restart) + cleanup(retcode, retmsg) ############################################################################### # pvc vm undefine @@ -385,15 +380,8 @@ def vm_undefine(domain): Stop virtual machine DOMAIN and remove it from the cluster database, preserving disks. DOMAIN may be a UUID or name. """ - # Ensure at least one search method is set - if domain == None: - click.echo("ERROR: You must specify either a name or UUID value.") - exit(1) - - # Open a Zookeeper connection - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.undefine_vm(zk_conn, domain, is_cli=True) - cleanup(retcode, retmsg, zk_conn) + retcode, retmsg = pvc_vm.vm_remove(config, domain, delete_disks=False) + cleanup(retcode, retmsg) ############################################################################### # pvc vm remove @@ -407,37 +395,8 @@ def vm_remove(domain): Stop virtual machine DOMAIN and remove it, along with all disks, from the cluster. DOMAIN may be a UUID or name. """ - # Ensure at least one search method is set - if domain == None: - click.echo("ERROR: You must specify either a name or UUID value.") - exit(1) - - # Open a Zookeeper connection - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.remove_vm(zk_conn, domain, is_cli=True) - cleanup(retcode, retmsg, zk_conn) - -############################################################################### -# pvc vm dump -############################################################################### -@click.command(name='dump', short_help='Dump a virtual machine XML to stdout.') -@click.argument( - 'domain' -) -def vm_dump(domain): - """ - Dump the Libvirt XML definition of virtual machine DOMAIN to stdout. DOMAIN may be a UUID or name. - """ - - # Ensure at least one search method is set - if domain == None: - click.echo("ERROR: You must specify either a name or UUID value.") - exit(1) - - # Open a Zookeeper connection - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.dump_vm(zk_conn, domain) - cleanup(retcode, retmsg, zk_conn) + retcode, retmsg = pvc_vm.vm_remove(config, domain, delete_disks=True) + cleanup(retcode, retmsg) ############################################################################### # pvc vm start @@ -451,10 +410,8 @@ def vm_start(domain): Start virtual machine DOMAIN on its configured node. DOMAIN may be a UUID or name. """ - # Open a Zookeeper connection - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.start_vm(zk_conn, domain) - cleanup(retcode, retmsg, zk_conn) + retcode, retmsg = pvc_vm.vm_state(config, domain, 'start') + cleanup(retcode, retmsg) ############################################################################### # pvc vm restart @@ -468,10 +425,8 @@ def vm_restart(domain): Restart running virtual machine DOMAIN. DOMAIN may be a UUID or name. """ - # Open a Zookeeper connection - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.restart_vm(zk_conn, domain) - cleanup(retcode, retmsg, zk_conn) + retcode, retmsg = pvc_vm.vm_state(config, domain, 'restart') + cleanup(retcode, retmsg) ############################################################################### # pvc vm shutdown @@ -485,10 +440,8 @@ def vm_shutdown(domain): Gracefully shut down virtual machine DOMAIN. DOMAIN may be a UUID or name. """ - # Open a Zookeeper connection - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.shutdown_vm(zk_conn, domain) - cleanup(retcode, retmsg, zk_conn) + retcode, retmsg = pvc_vm.vm_state(config, domain, 'shutdown') + cleanup(retcode, retmsg) ############################################################################### # pvc vm stop @@ -502,10 +455,8 @@ def vm_stop(domain): Forcibly halt (destroy) running virtual machine DOMAIN. DOMAIN may be a UUID or name. """ - # Open a Zookeeper connection - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.stop_vm(zk_conn, domain) - cleanup(retcode, retmsg, zk_conn) + retcode, retmsg = pvc_vm.vm_state(config, domain, 'stop') + cleanup(retcode, retmsg) ############################################################################### # pvc vm disable @@ -521,10 +472,8 @@ def vm_disable(domain): Use this option for VM that are stopped intentionally or long-term and which should not impact cluster health if stopped. A VM can be started directly from disable state. """ - # Open a Zookeeper connection - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.disable_vm(zk_conn, domain) - cleanup(retcode, retmsg, zk_conn) + retcode, retmsg = pvc_vm.vm_state(config, domain, 'disable') + cleanup(retcode, retmsg) ############################################################################### # pvc vm move @@ -542,10 +491,8 @@ def vm_move(domain, target_node): Permanently move virtual machine DOMAIN, via live migration if running and possible, to another node. DOMAIN may be a UUID or name. """ - # Open a Zookeeper connection - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.move_vm(zk_conn, domain, target_node) - cleanup(retcode, retmsg, zk_conn) + retcode, retmsg = pvc_vm.vm_node(config, domain, target_node, 'move', force=False) + cleanup(retcode, retmsg) ############################################################################### # pvc vm migrate @@ -567,10 +514,8 @@ def vm_migrate(domain, target_node, force_migrate): Temporarily migrate running virtual machine DOMAIN, via live migration if possible, to another node. DOMAIN may be a UUID or name. If DOMAIN is not running, it will be started on the target node. """ - # Open a Zookeeper connection - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.migrate_vm(zk_conn, domain, target_node, force_migrate, is_cli=True) - cleanup(retcode, retmsg, zk_conn) + retcode, retmsg = pvc_vm.vm_node(config, domain, target_node, 'migrate', force=force_migrate) + cleanup(retcode, retmsg) ############################################################################### # pvc vm unmigrate @@ -584,10 +529,8 @@ def vm_unmigrate(domain): Restore previously migrated virtual machine DOMAIN, via live migration if possible, to its original node. DOMAIN may be a UUID or name. If DOMAIN is not running, it will be started on the target node. """ - # Open a Zookeeper connection - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.unmigrate_vm(zk_conn, domain) - cleanup(retcode, retmsg, zk_conn) + retcode, retmsg = pvc_vm.vm_node(config, domain, None, 'unmigrate', force=False) + cleanup(retcode, retmsg) ############################################################################### # pvc vm flush-locks @@ -601,34 +544,8 @@ def vm_flush_locks(domain): Flush stale RBD locks for virtual machine DOMAIN. DOMAIN may be a UUID or name. DOMAIN must be in a stopped state before flushing locks. """ - # Open a Zookeeper connection - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retmsg = pvc_vm.flush_locks(zk_conn, domain) - cleanup(retcode, retmsg, zk_conn) - -############################################################################### -# pvc vm info -############################################################################### -@click.command(name='info', short_help='Show details of a VM object.') -@click.argument( - 'domain' -) -@click.option( - '-l', '--long', 'long_output', is_flag=True, default=False, - help='Display more detailed information.' -) -def vm_info(domain, long_output): - """ - Show information about virtual machine DOMAIN. DOMAIN may be a UUID or name. - """ - - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retdata = pvc_vm.get_info(zk_conn, domain) - if retcode: - pvc_vm.format_info(zk_conn, retdata, long_output) - retdata = '' - cleanup(retcode, retdata, zk_conn) - + retcode, retmsg = pvc_vm.vm_locks(config, domain) + cleanup(retcode, retmsg) ############################################################################### # pvc vm log @@ -656,6 +573,50 @@ def vm_log(domain, lines, follow): retcode, retmsg = pvc_vm.view_console_log(config, domain, lines) cleanup(retcode, retmsg) +############################################################################### +# pvc vm info +############################################################################### +@click.command(name='info', short_help='Show details of a VM object.') +@click.argument( + 'domain' +) +@click.option( + '-l', '--long', 'long_output', is_flag=True, default=False, + help='Display more detailed information.' +) +def vm_info(domain, long_output): + """ + Show information about virtual machine DOMAIN. DOMAIN may be a UUID or name. + """ + + retcode, retdata = pvc_vm.vm_info(config, domain) + if retcode: + pvc_vm.format_info(config, retdata, long_output) + retdata = '' + cleanup(retcode, retdata) + +############################################################################### +# pvc vm dump +############################################################################### +@click.command(name='dump', short_help='Dump a virtual machine XML to stdout.') +@click.argument( + 'domain' +) +def vm_dump(domain): + """ + Dump the Libvirt XML definition of virtual machine DOMAIN to stdout. DOMAIN may be a UUID or name. + """ + + retcode, vm_information = pvc_vm.vm_info(config, domain) + if not retcode and not vm_information.get('name', None): + cleanup(False, 'ERROR: Could not find VM "{}" in the cluster!'.format(domain)) + + # Grab the current config + current_vm_cfg_raw = vm_information.get('xml') + xml_data = etree.fromstring(current_vm_cfg_raw) + current_vm_cfgfile = etree.tostring(xml_data, pretty_print=True).decode('utf8') + click.echo(current_vm_cfgfile.strip()) + ############################################################################### # pvc vm list ############################################################################### @@ -682,12 +643,11 @@ def vm_list(target_node, target_state, limit, raw): NOTE: Red-coloured network lists indicate one or more configured networks are missing/invalid. """ - zk_conn = pvc_common.startZKConnection(zk_host) - retcode, retdata = pvc_vm.get_list(zk_conn, target_node, target_state, limit) + retcode, retdata = pvc_vm.vm_list(config, limit, target_node, target_state) if retcode: - pvc_vm.format_list(zk_conn, retdata, raw) + pvc_vm.format_list(config, retdata, raw) retdata = '' - cleanup(retcode, retdata, zk_conn) + cleanup(retcode, retdata) ############################################################################### # pvc network