Add VM device hot attach/detach support
Adds a new API endpoint to support hot attach/detach of devices, and the corresponding client-side logic to use this endpoint when doing VM network/storage add/remove actions. The live attach is now the default behaviour for these types of additions and removals, and can be disabled if needed. Closes #141
This commit is contained in:
		@@ -134,6 +134,48 @@ def vm_modify(config, vm, xml, restart):
 | 
			
		||||
    return retstatus, response.json().get('message', '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def vm_device_attach(config, vm, xml):
 | 
			
		||||
    """
 | 
			
		||||
    Attach a device to a VM
 | 
			
		||||
 | 
			
		||||
    API endpoint: POST /vm/{vm}/device
 | 
			
		||||
    API arguments: xml={xml}
 | 
			
		||||
    API schema: {"message":"{data}"}
 | 
			
		||||
    """
 | 
			
		||||
    data = {
 | 
			
		||||
        'xml': xml
 | 
			
		||||
    }
 | 
			
		||||
    response = call_api(config, 'post', '/vm/{vm}/device'.format(vm=vm), data=data)
 | 
			
		||||
 | 
			
		||||
    if response.status_code == 200:
 | 
			
		||||
        retstatus = True
 | 
			
		||||
    else:
 | 
			
		||||
        retstatus = False
 | 
			
		||||
 | 
			
		||||
    return retstatus, response.json().get('message', '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def vm_device_detach(config, vm, xml):
 | 
			
		||||
    """
 | 
			
		||||
    Detach a device from a VM
 | 
			
		||||
 | 
			
		||||
    API endpoint: DELETE /vm/{vm}/device
 | 
			
		||||
    API arguments: xml={xml}
 | 
			
		||||
    API schema: {"message":"{data}"}
 | 
			
		||||
    """
 | 
			
		||||
    data = {
 | 
			
		||||
        'xml': xml
 | 
			
		||||
    }
 | 
			
		||||
    response = call_api(config, 'delete', '/vm/{vm}/device'.format(vm=vm), data=data)
 | 
			
		||||
 | 
			
		||||
    if response.status_code == 200:
 | 
			
		||||
        retstatus = True
 | 
			
		||||
    else:
 | 
			
		||||
        retstatus = False
 | 
			
		||||
 | 
			
		||||
    return retstatus, response.json().get('message', '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def vm_rename(config, vm, new_name):
 | 
			
		||||
    """
 | 
			
		||||
    Rename VM to new name
 | 
			
		||||
@@ -618,13 +660,15 @@ def format_vm_memory(config, name, memory):
 | 
			
		||||
    return '\n'.join(output_list)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def vm_networks_add(config, vm, network, macaddr, model, sriov, sriov_mode, restart):
 | 
			
		||||
def vm_networks_add(config, vm, network, macaddr, model, sriov, sriov_mode, live, restart):
 | 
			
		||||
    """
 | 
			
		||||
    Add a new network to the VM
 | 
			
		||||
 | 
			
		||||
    Calls vm_info to get the VM XML.
 | 
			
		||||
 | 
			
		||||
    Calls vm_modify to set the VM XML.
 | 
			
		||||
 | 
			
		||||
    Calls vm_device_attach if live to hot-attach the device.
 | 
			
		||||
    """
 | 
			
		||||
    from lxml.objectify import fromstring
 | 
			
		||||
    from lxml.etree import tostring
 | 
			
		||||
@@ -747,16 +791,36 @@ def vm_networks_add(config, vm, network, macaddr, model, sriov, sriov_mode, rest
 | 
			
		||||
    except Exception:
 | 
			
		||||
        return False, 'ERROR: Failed to dump XML data.'
 | 
			
		||||
 | 
			
		||||
    return vm_modify(config, vm, new_xml, restart)
 | 
			
		||||
    modify_retcode, modify_retmsg = vm_modify(config, vm, new_xml, restart)
 | 
			
		||||
 | 
			
		||||
    if not modify_retcode:
 | 
			
		||||
        return modify_retcode, modify_retmsg
 | 
			
		||||
 | 
			
		||||
    if live:
 | 
			
		||||
        attach_retcode, attach_retmsg = vm_device_attach(config, vm, device_string)
 | 
			
		||||
 | 
			
		||||
        if not attach_retcode:
 | 
			
		||||
            retcode = attach_retcode
 | 
			
		||||
            retmsg = attach_retmsg
 | 
			
		||||
        else:
 | 
			
		||||
            retcode = attach_retcode
 | 
			
		||||
            retmsg = "Network '{}' successfully added to VM config and hot attached to running VM.".format(network)
 | 
			
		||||
    else:
 | 
			
		||||
        retcode = modify_retcode
 | 
			
		||||
        retmsg = modify_retmsg
 | 
			
		||||
 | 
			
		||||
    return retcode, retmsg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def vm_networks_remove(config, vm, network, sriov, restart):
 | 
			
		||||
def vm_networks_remove(config, vm, network, sriov, live, restart):
 | 
			
		||||
    """
 | 
			
		||||
    Remove a network to the VM
 | 
			
		||||
 | 
			
		||||
    Calls vm_info to get the VM XML.
 | 
			
		||||
 | 
			
		||||
    Calls vm_modify to set the VM XML.
 | 
			
		||||
 | 
			
		||||
    Calls vm_device_detach to hot-remove the device.
 | 
			
		||||
    """
 | 
			
		||||
    from lxml.objectify import fromstring
 | 
			
		||||
    from lxml.etree import tostring
 | 
			
		||||
@@ -775,6 +839,7 @@ def vm_networks_remove(config, vm, network, sriov, restart):
 | 
			
		||||
        return False, 'ERROR: Failed to parse XML data.'
 | 
			
		||||
 | 
			
		||||
    changed = False
 | 
			
		||||
    device_string = None
 | 
			
		||||
    for interface in parsed_xml.devices.find('interface'):
 | 
			
		||||
        if sriov:
 | 
			
		||||
            if interface.attrib.get('type') == 'hostdev':
 | 
			
		||||
@@ -792,16 +857,37 @@ def vm_networks_remove(config, vm, network, sriov, restart):
 | 
			
		||||
            if network == if_vni:
 | 
			
		||||
                interface.getparent().remove(interface)
 | 
			
		||||
                changed = True
 | 
			
		||||
        if changed:
 | 
			
		||||
            device_string = tostring(interface)
 | 
			
		||||
 | 
			
		||||
    if changed:
 | 
			
		||||
        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)
 | 
			
		||||
    else:
 | 
			
		||||
        return False, 'ERROR: Network "{}" does not exist on VM.'.format(network)
 | 
			
		||||
 | 
			
		||||
    modify_retcode, modify_retmsg = vm_modify(config, vm, new_xml, restart)
 | 
			
		||||
 | 
			
		||||
    if not modify_retcode:
 | 
			
		||||
        return modify_retcode, modify_retmsg
 | 
			
		||||
 | 
			
		||||
    if live and device_string:
 | 
			
		||||
        detach_retcode, detach_retmsg = vm_device_detach(config, vm, device_string)
 | 
			
		||||
 | 
			
		||||
        if not detach_retcode:
 | 
			
		||||
            retcode = detach_retcode
 | 
			
		||||
            retmsg = detach_retmsg
 | 
			
		||||
        else:
 | 
			
		||||
            retcode = detach_retcode
 | 
			
		||||
            retmsg = "Network '{}' successfully removed from VM config and hot detached from running VM.".format(network)
 | 
			
		||||
    else:
 | 
			
		||||
        retcode = modify_retcode
 | 
			
		||||
        retmsg = modify_retmsg
 | 
			
		||||
 | 
			
		||||
    return retcode, retmsg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def vm_networks_get(config, vm):
 | 
			
		||||
    """
 | 
			
		||||
@@ -913,7 +999,7 @@ def format_vm_networks(config, name, networks):
 | 
			
		||||
    return '\n'.join(output_list)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def vm_volumes_add(config, vm, volume, disk_id, bus, disk_type, restart):
 | 
			
		||||
def vm_volumes_add(config, vm, volume, disk_id, bus, disk_type, live, restart):
 | 
			
		||||
    """
 | 
			
		||||
    Add a new volume to the VM
 | 
			
		||||
 | 
			
		||||
@@ -1001,6 +1087,7 @@ def vm_volumes_add(config, vm, volume, disk_id, bus, disk_type, restart):
 | 
			
		||||
        new_disk_details.source.set('name', volume)
 | 
			
		||||
    elif disk_type == 'file':
 | 
			
		||||
        new_disk_details.source.set('file', volume)
 | 
			
		||||
    device_xml = new_disk_details
 | 
			
		||||
 | 
			
		||||
    all_disks = parsed_xml.devices.find('disk')
 | 
			
		||||
    if all_disks is None:
 | 
			
		||||
@@ -1008,18 +1095,42 @@ def vm_volumes_add(config, vm, volume, disk_id, bus, disk_type, restart):
 | 
			
		||||
    for disk in all_disks:
 | 
			
		||||
        last_disk = disk
 | 
			
		||||
 | 
			
		||||
    if last_disk is None:
 | 
			
		||||
        parsed_xml.devices.find('emulator').addprevious(new_disk_details)
 | 
			
		||||
    # Add the disk at the end of the list (or, right above emulator)
 | 
			
		||||
    if len(all_disks) > 0:
 | 
			
		||||
        for idx, disk in enumerate(parsed_xml.devices.find('disk')):
 | 
			
		||||
            if idx == len(all_disks) - 1:
 | 
			
		||||
                disk.addnext(device_xml)
 | 
			
		||||
    else:
 | 
			
		||||
        parsed_xml.devices.find('emulator').addprevious(device_xml)
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
    modify_retcode, modify_retmsg = vm_modify(config, vm, new_xml, restart)
 | 
			
		||||
 | 
			
		||||
    if not modify_retcode:
 | 
			
		||||
        return modify_retcode, modify_retmsg
 | 
			
		||||
 | 
			
		||||
    if live:
 | 
			
		||||
        device_string = tostring(device_xml)
 | 
			
		||||
        attach_retcode, attach_retmsg = vm_device_attach(config, vm, device_string)
 | 
			
		||||
 | 
			
		||||
        if not attach_retcode:
 | 
			
		||||
            retcode = attach_retcode
 | 
			
		||||
            retmsg = attach_retmsg
 | 
			
		||||
        else:
 | 
			
		||||
            retcode = attach_retcode
 | 
			
		||||
            retmsg = "Volume '{}/{}' successfully added to VM config and hot attached to running VM.".format(vpool, vname)
 | 
			
		||||
    else:
 | 
			
		||||
        retcode = modify_retcode
 | 
			
		||||
        retmsg = modify_retmsg
 | 
			
		||||
 | 
			
		||||
    return retcode, retmsg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def vm_volumes_remove(config, vm, volume, restart):
 | 
			
		||||
def vm_volumes_remove(config, vm, volume, live, restart):
 | 
			
		||||
    """
 | 
			
		||||
    Remove a volume to the VM
 | 
			
		||||
 | 
			
		||||
@@ -1043,19 +1154,44 @@ def vm_volumes_remove(config, vm, volume, restart):
 | 
			
		||||
    except Exception:
 | 
			
		||||
        return False, 'ERROR: Failed to parse XML data.'
 | 
			
		||||
 | 
			
		||||
    changed = False
 | 
			
		||||
    device_string = None
 | 
			
		||||
    for disk in parsed_xml.devices.find('disk'):
 | 
			
		||||
        disk_name = disk.source.attrib.get('name')
 | 
			
		||||
        if not disk_name:
 | 
			
		||||
            disk_name = disk.source.attrib.get('file')
 | 
			
		||||
        if volume == disk_name:
 | 
			
		||||
            device_string = tostring(disk)
 | 
			
		||||
            disk.getparent().remove(disk)
 | 
			
		||||
            changed = True
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        new_xml = tostring(parsed_xml, pretty_print=True)
 | 
			
		||||
    except Exception:
 | 
			
		||||
        return False, 'ERROR: Failed to dump XML data.'
 | 
			
		||||
    if changed:
 | 
			
		||||
        try:
 | 
			
		||||
            new_xml = tostring(parsed_xml, pretty_print=True)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            return False, 'ERROR: Failed to dump XML data.'
 | 
			
		||||
    else:
 | 
			
		||||
        return False, 'ERROR: Volume "{}" does not exist on VM.'.format(volume)
 | 
			
		||||
 | 
			
		||||
    return vm_modify(config, vm, new_xml, restart)
 | 
			
		||||
    modify_retcode, modify_retmsg = vm_modify(config, vm, new_xml, restart)
 | 
			
		||||
 | 
			
		||||
    if not modify_retcode:
 | 
			
		||||
        return modify_retcode, modify_retmsg
 | 
			
		||||
 | 
			
		||||
    if live and device_string:
 | 
			
		||||
        detach_retcode, detach_retmsg = vm_device_detach(config, vm, device_string)
 | 
			
		||||
 | 
			
		||||
        if not detach_retcode:
 | 
			
		||||
            retcode = detach_retcode
 | 
			
		||||
            retmsg = detach_retmsg
 | 
			
		||||
        else:
 | 
			
		||||
            retcode = detach_retcode
 | 
			
		||||
            retmsg = "Volume '{}' successfully removed from VM config and hot detached from running VM.".format(volume)
 | 
			
		||||
    else:
 | 
			
		||||
        retcode = modify_retcode
 | 
			
		||||
        retmsg = modify_retmsg
 | 
			
		||||
 | 
			
		||||
    return retcode, retmsg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def vm_volumes_get(config, vm):
 | 
			
		||||
 
 | 
			
		||||
@@ -1472,7 +1472,7 @@ def vm_network_get(domain, raw):
 | 
			
		||||
    help='The model for the interface; must be a valid libvirt model. Not used for "netdev" SR-IOV NETs.'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-s', '--sriov', 'sriov', is_flag=True, default=False,
 | 
			
		||||
    '-s', '--sriov', 'sriov_flag', is_flag=True, default=False,
 | 
			
		||||
    help='Identify that NET is an SR-IOV device name and not a VNI. Required for adding SR-IOV NETs.'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
@@ -1481,16 +1481,20 @@ def vm_network_get(domain, raw):
 | 
			
		||||
    help='For SR-IOV NETs, the SR-IOV network device mode.'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-r', '--restart', 'restart', is_flag=True, default=False,
 | 
			
		||||
    help='Immediately restart VM to apply new config.'
 | 
			
		||||
    '-l/-L', '--live/--no-live', 'live_flag', is_flag=True, default=True,
 | 
			
		||||
    help='Immediately live-attach device to VM [default] or disable this behaviour.'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-r', '--restart', 'restart_flag', is_flag=True, default=False,
 | 
			
		||||
    help='Immediately restart VM to apply new config; requires "--no-live".'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-y', '--yes', 'confirm_flag',
 | 
			
		||||
    is_flag=True, default=False,
 | 
			
		||||
    help='Confirm the restart'
 | 
			
		||||
    help='Confirm the VM restart.'
 | 
			
		||||
)
 | 
			
		||||
@cluster_req
 | 
			
		||||
def vm_network_add(domain, net, macaddr, model, sriov, sriov_mode, restart, confirm_flag):
 | 
			
		||||
def vm_network_add(domain, net, macaddr, model, sriov_flag, sriov_mode, live_flag, restart_flag, confirm_flag):
 | 
			
		||||
    """
 | 
			
		||||
    Add the network NET to the virtual machine DOMAIN. Networks are always addded to the end of the current list of networks in the virtual machine.
 | 
			
		||||
 | 
			
		||||
@@ -1503,15 +1507,17 @@ def vm_network_add(domain, net, macaddr, model, sriov, sriov_mode, restart, conf
 | 
			
		||||
      2. If an identical SR-IOV VF device is not present on the target node, post-migration startup will fail. It may be prudent to use a node limit here.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    if restart and not confirm_flag and not config['unsafe']:
 | 
			
		||||
    if restart_flag and live_flag:
 | 
			
		||||
        click.echo('WARNING: Live flag and restart flag both specified; this can cause unintended behaviour. To disable live changes, use "--no-live".')
 | 
			
		||||
        exit(1)
 | 
			
		||||
 | 
			
		||||
    if restart_flag and not confirm_flag and not config['unsafe']:
 | 
			
		||||
        try:
 | 
			
		||||
            click.confirm('Restart VM {}'.format(domain), prompt_suffix='? ', abort=True)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            restart = False
 | 
			
		||||
            restart_flag = False
 | 
			
		||||
 | 
			
		||||
    retcode, retmsg = pvc_vm.vm_networks_add(config, domain, net, macaddr, model, sriov, sriov_mode, restart)
 | 
			
		||||
    if retcode and not restart:
 | 
			
		||||
        retmsg = retmsg + " Changes will be applied on next VM start/restart."
 | 
			
		||||
    retcode, retmsg = pvc_vm.vm_networks_add(config, domain, net, macaddr, model, sriov_flag, sriov_mode, live_flag, restart_flag)
 | 
			
		||||
    cleanup(retcode, retmsg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1526,34 +1532,40 @@ def vm_network_add(domain, net, macaddr, model, sriov, sriov_mode, restart, conf
 | 
			
		||||
    'net'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-s', '--sriov', 'sriov', is_flag=True, default=False,
 | 
			
		||||
    '-s', '--sriov', 'sriov_flag', is_flag=True, default=False,
 | 
			
		||||
    help='Identify that NET is an SR-IOV device name and not a VNI. Required for removing SR-IOV NETs.'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-r', '--restart', 'restart', is_flag=True, default=False,
 | 
			
		||||
    help='Immediately restart VM to apply new config.'
 | 
			
		||||
    '-l/-L', '--live/--no-live', 'live_flag', is_flag=True, default=True,
 | 
			
		||||
    help='Immediately live-attach device to VM [default] or disable this behaviour.'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-r', '--restart', 'restart_flag', is_flag=True, default=False,
 | 
			
		||||
    help='Immediately restart VM to apply new config; requires "--no-live".'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-y', '--yes', 'confirm_flag',
 | 
			
		||||
    is_flag=True, default=False,
 | 
			
		||||
    help='Confirm the restart'
 | 
			
		||||
    help='Confirm the restart.'
 | 
			
		||||
)
 | 
			
		||||
@cluster_req
 | 
			
		||||
def vm_network_remove(domain, net, sriov, restart, confirm_flag):
 | 
			
		||||
def vm_network_remove(domain, net, sriov_flag, live_flag, restart_flag, confirm_flag):
 | 
			
		||||
    """
 | 
			
		||||
    Remove the network NET from the virtual machine DOMAIN.
 | 
			
		||||
 | 
			
		||||
    NET may be a PVC network VNI, which is added as a bridged device, or a SR-IOV VF device connected in the given mode.
 | 
			
		||||
    """
 | 
			
		||||
    if restart and not confirm_flag and not config['unsafe']:
 | 
			
		||||
    if restart_flag and live_flag:
 | 
			
		||||
        click.echo('WARNING: Live flag and restart flag both specified; this can cause unintended behaviour. To disable live changes, use "--no-live".')
 | 
			
		||||
        exit(1)
 | 
			
		||||
 | 
			
		||||
    if restart_flag and not confirm_flag and not config['unsafe']:
 | 
			
		||||
        try:
 | 
			
		||||
            click.confirm('Restart VM {}'.format(domain), prompt_suffix='? ', abort=True)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            restart = False
 | 
			
		||||
            restart_flag = False
 | 
			
		||||
 | 
			
		||||
    retcode, retmsg = pvc_vm.vm_networks_remove(config, domain, net, sriov, restart)
 | 
			
		||||
    if retcode and not restart:
 | 
			
		||||
        retmsg = retmsg + " Changes will be applied on next VM start/restart."
 | 
			
		||||
    retcode, retmsg = pvc_vm.vm_networks_remove(config, domain, net, sriov_flag, live_flag, restart_flag)
 | 
			
		||||
    cleanup(retcode, retmsg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1623,8 +1635,12 @@ def vm_volume_get(domain, raw):
 | 
			
		||||
    help='The type of volume to add.'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-r', '--restart', 'restart', is_flag=True, default=False,
 | 
			
		||||
    help='Immediately restart VM to apply new config.'
 | 
			
		||||
    '-l/-L', '--live/--no-live', 'live_flag', is_flag=True, default=True,
 | 
			
		||||
    help='Immediately live-attach device to VM [default] or disable this behaviour.'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-r', '--restart', 'restart_flag', is_flag=True, default=False,
 | 
			
		||||
    help='Immediately restart VM to apply new config; requires "--no-live".'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-y', '--yes', 'confirm_flag',
 | 
			
		||||
@@ -1632,21 +1648,23 @@ def vm_volume_get(domain, raw):
 | 
			
		||||
    help='Confirm the restart'
 | 
			
		||||
)
 | 
			
		||||
@cluster_req
 | 
			
		||||
def vm_volume_add(domain, volume, disk_id, bus, disk_type, restart, confirm_flag):
 | 
			
		||||
def vm_volume_add(domain, volume, disk_id, bus, disk_type, live_flag, restart_flag, confirm_flag):
 | 
			
		||||
    """
 | 
			
		||||
    Add the volume VOLUME to the virtual machine DOMAIN.
 | 
			
		||||
 | 
			
		||||
    VOLUME may be either an absolute file path (for type 'file') or an RBD volume in the form "pool/volume" (for type 'rbd'). RBD volumes are verified against the cluster before adding and must exist.
 | 
			
		||||
    """
 | 
			
		||||
    if restart and not confirm_flag and not config['unsafe']:
 | 
			
		||||
    if restart_flag and live_flag:
 | 
			
		||||
        click.echo('WARNING: Live flag and restart flag both specified; this can cause unintended behaviour. To disable live changes, use "--no-live".')
 | 
			
		||||
        exit(1)
 | 
			
		||||
 | 
			
		||||
    if restart_flag and not confirm_flag and not config['unsafe']:
 | 
			
		||||
        try:
 | 
			
		||||
            click.confirm('Restart VM {}'.format(domain), prompt_suffix='? ', abort=True)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            restart = False
 | 
			
		||||
            restart_flag = False
 | 
			
		||||
 | 
			
		||||
    retcode, retmsg = pvc_vm.vm_volumes_add(config, domain, volume, disk_id, bus, disk_type, restart)
 | 
			
		||||
    if retcode and not restart:
 | 
			
		||||
        retmsg = retmsg + " Changes will be applied on next VM start/restart."
 | 
			
		||||
    retcode, retmsg = pvc_vm.vm_volumes_add(config, domain, volume, disk_id, bus, disk_type, live_flag, restart_flag)
 | 
			
		||||
    cleanup(retcode, retmsg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1661,8 +1679,12 @@ def vm_volume_add(domain, volume, disk_id, bus, disk_type, restart, confirm_flag
 | 
			
		||||
    'volume'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-r', '--restart', 'restart', is_flag=True, default=False,
 | 
			
		||||
    help='Immediately restart VM to apply new config.'
 | 
			
		||||
    '-l/-L', '--live/--no-live', 'live_flag', is_flag=True, default=True,
 | 
			
		||||
    help='Immediately live-attach device to VM [default] or disable this behaviour.'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-r', '--restart', 'restart_flag', is_flag=True, default=False,
 | 
			
		||||
    help='Immediately restart VM to apply new config; requires "--no-live".'
 | 
			
		||||
)
 | 
			
		||||
@click.option(
 | 
			
		||||
    '-y', '--yes', 'confirm_flag',
 | 
			
		||||
@@ -1670,19 +1692,21 @@ def vm_volume_add(domain, volume, disk_id, bus, disk_type, restart, confirm_flag
 | 
			
		||||
    help='Confirm the restart'
 | 
			
		||||
)
 | 
			
		||||
@cluster_req
 | 
			
		||||
def vm_volume_remove(domain, volume, restart, confirm_flag):
 | 
			
		||||
def vm_volume_remove(domain, volume, live_flag, restart_flag, confirm_flag):
 | 
			
		||||
    """
 | 
			
		||||
    Remove VOLUME from the virtual machine DOMAIN; VOLUME must be a file path or RBD path in 'pool/volume' format.
 | 
			
		||||
    """
 | 
			
		||||
    if restart and not confirm_flag and not config['unsafe']:
 | 
			
		||||
    if restart_flag and live_flag:
 | 
			
		||||
        click.echo('WARNING: Live flag and restart flag both specified; this can cause unintended behaviour. To disable live changes, use "--no-live".')
 | 
			
		||||
        exit(1)
 | 
			
		||||
 | 
			
		||||
    if restart_flag and not confirm_flag and not config['unsafe']:
 | 
			
		||||
        try:
 | 
			
		||||
            click.confirm('Restart VM {}'.format(domain), prompt_suffix='? ', abort=True)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            restart = False
 | 
			
		||||
            restart_flag = False
 | 
			
		||||
 | 
			
		||||
    retcode, retmsg = pvc_vm.vm_volumes_remove(config, domain, volume, restart)
 | 
			
		||||
    if retcode and not restart:
 | 
			
		||||
        retmsg = retmsg + " Changes will be applied on next VM start/restart."
 | 
			
		||||
    retcode, retmsg = pvc_vm.vm_volumes_remove(config, domain, volume, live_flag, restart_flag)
 | 
			
		||||
    cleanup(retcode, retmsg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user