Implement network modification on the CLI
Adds functions for listing, adding, and removing networks from the CLI, without editing the XML directly. References #101
This commit is contained in:
parent
18d3fc8431
commit
1ff5d8bf46
|
@ -369,8 +369,8 @@ def format_vm_vcpus(config, name, vcpus):
|
||||||
sockets_length=sockets_length,
|
sockets_length=sockets_length,
|
||||||
cores_length=cores_length,
|
cores_length=cores_length,
|
||||||
threads_length=threads_length,
|
threads_length=threads_length,
|
||||||
bold=ansiprint.bold(),
|
bold='',
|
||||||
end_bold=ansiprint.end(),
|
end_bold='',
|
||||||
name=name,
|
name=name,
|
||||||
vcpus=vcpus[0],
|
vcpus=vcpus[0],
|
||||||
sockets=vcpus[1][0],
|
sockets=vcpus[1][0],
|
||||||
|
@ -472,8 +472,8 @@ def format_vm_memory(config, name, memory):
|
||||||
{memory: <{memory_length}}{end_bold}'.format(
|
{memory: <{memory_length}}{end_bold}'.format(
|
||||||
name_length=name_length,
|
name_length=name_length,
|
||||||
memory_length=memory_length,
|
memory_length=memory_length,
|
||||||
bold=ansiprint.bold(),
|
bold='',
|
||||||
end_bold=ansiprint.end(),
|
end_bold='',
|
||||||
name=name,
|
name=name,
|
||||||
memory=memory
|
memory=memory
|
||||||
)
|
)
|
||||||
|
@ -481,6 +481,207 @@ def format_vm_memory(config, name, memory):
|
||||||
return '\n'.join(output_list)
|
return '\n'.join(output_list)
|
||||||
|
|
||||||
|
|
||||||
|
def vm_networks_add(config, vm, network, macaddr, model, restart):
|
||||||
|
"""
|
||||||
|
Add a new network to the VM
|
||||||
|
|
||||||
|
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
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
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.'
|
||||||
|
|
||||||
|
if macaddr is None:
|
||||||
|
mac_prefix = '52:54:00'
|
||||||
|
random_octet_A = '{:x}'.format(randint(16, 238))
|
||||||
|
random_octet_B = '{:x}'.format(randint(16, 238))
|
||||||
|
random_octet_C = '{:x}'.format(randint(16, 238))
|
||||||
|
macaddr = '{prefix}:{octetA}:{octetB}:{octetC}'.format(
|
||||||
|
prefix=mac_prefix,
|
||||||
|
octetA=random_octet_A,
|
||||||
|
octetB=random_octet_B,
|
||||||
|
octetC=random_octet_C
|
||||||
|
)
|
||||||
|
|
||||||
|
device_string = '<interface type="bridge"><mac address="{macaddr}"/><source bridge="{bridge}"/><model type="{model}"/></interface>'.format(
|
||||||
|
macaddr=macaddr,
|
||||||
|
bridge="vmbr{}".format(network),
|
||||||
|
model=model
|
||||||
|
)
|
||||||
|
device_xml = fromstring(device_string)
|
||||||
|
|
||||||
|
last_interface = None
|
||||||
|
for interface in parsed_xml.devices.find('interface'):
|
||||||
|
last_interface = re.match(r'vmbr([0-9]+)', interface.source.attrib.get('bridge')).group(1)
|
||||||
|
if last_interface == network:
|
||||||
|
return False, 'Network {} is already configured for VM {}.'.format(network, vm)
|
||||||
|
if last_interface is not None:
|
||||||
|
for interface in parsed_xml.devices.find('interface'):
|
||||||
|
if last_interface == re.match(r'vmbr([0-9]+)', interface.source.attrib.get('bridge')).group(1):
|
||||||
|
interface.addnext(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)
|
||||||
|
|
||||||
|
|
||||||
|
def vm_networks_remove(config, vm, network, restart):
|
||||||
|
"""
|
||||||
|
Remove a network to the VM
|
||||||
|
|
||||||
|
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.'
|
||||||
|
|
||||||
|
for interface in parsed_xml.devices.find('interface'):
|
||||||
|
if_vni = re.match(r'vmbr([0-9]+)', interface.source.attrib.get('bridge')).group(1)
|
||||||
|
if network == if_vni:
|
||||||
|
interface.getparent().remove(interface)
|
||||||
|
|
||||||
|
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_networks_get(config, vm):
|
||||||
|
"""
|
||||||
|
Get the networks of the VM
|
||||||
|
|
||||||
|
Calls vm_info to get VM XML.
|
||||||
|
|
||||||
|
Returns a tuple of (network, (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.'
|
||||||
|
|
||||||
|
network_data = list()
|
||||||
|
for interface in parsed_xml.devices.find('interface'):
|
||||||
|
mac_address = interface.mac.attrib.get('address')
|
||||||
|
model = interface.model.attrib.get('type')
|
||||||
|
network = re.match(r'vmbr([0-9]+)', interface.source.attrib.get('bridge')).group(1)
|
||||||
|
network_data.append((network, mac_address, model))
|
||||||
|
|
||||||
|
return True, network_data
|
||||||
|
|
||||||
|
|
||||||
|
def format_vm_networks(config, name, networks):
|
||||||
|
"""
|
||||||
|
Format the output of a network value in a nice table
|
||||||
|
"""
|
||||||
|
output_list = []
|
||||||
|
|
||||||
|
name_length = 5
|
||||||
|
_name_length = len(name) + 1
|
||||||
|
if _name_length > name_length:
|
||||||
|
name_length = _name_length
|
||||||
|
|
||||||
|
for network in networks:
|
||||||
|
vni_length = 8
|
||||||
|
_vni_length = len(network[0]) + 1
|
||||||
|
if _vni_length > vni_length:
|
||||||
|
vni_length = _vni_length
|
||||||
|
|
||||||
|
macaddr_length = 12
|
||||||
|
_macaddr_length = len(network[1]) + 1
|
||||||
|
if _macaddr_length > macaddr_length:
|
||||||
|
macaddr_length = _macaddr_length
|
||||||
|
|
||||||
|
model_length = 6
|
||||||
|
_model_length = len(network[2]) + 1
|
||||||
|
if _model_length > model_length:
|
||||||
|
model_length = _model_length
|
||||||
|
|
||||||
|
output_list.append(
|
||||||
|
'{bold}{name: <{name_length}} \
|
||||||
|
{vni: <{vni_length}} \
|
||||||
|
{macaddr: <{macaddr_length}} \
|
||||||
|
{model: <{model_length}}{end_bold}'.format(
|
||||||
|
name_length=name_length,
|
||||||
|
vni_length=vni_length,
|
||||||
|
macaddr_length=macaddr_length,
|
||||||
|
model_length=model_length,
|
||||||
|
bold=ansiprint.bold(),
|
||||||
|
end_bold=ansiprint.end(),
|
||||||
|
name='Name',
|
||||||
|
vni='Network',
|
||||||
|
macaddr='MAC Address',
|
||||||
|
model='Model'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
count = 0
|
||||||
|
for network in networks:
|
||||||
|
if count > 0:
|
||||||
|
name = ''
|
||||||
|
count += 1
|
||||||
|
output_list.append(
|
||||||
|
'{bold}{name: <{name_length}} \
|
||||||
|
{vni: <{vni_length}} \
|
||||||
|
{macaddr: <{macaddr_length}} \
|
||||||
|
{model: <{model_length}}{end_bold}'.format(
|
||||||
|
name_length=name_length,
|
||||||
|
vni_length=vni_length,
|
||||||
|
macaddr_length=macaddr_length,
|
||||||
|
model_length=model_length,
|
||||||
|
bold='',
|
||||||
|
end_bold='',
|
||||||
|
name=name,
|
||||||
|
vni=network[0],
|
||||||
|
macaddr=network[1],
|
||||||
|
model=network[2]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return '\n'.join(output_list)
|
||||||
|
|
||||||
|
|
||||||
def view_console_log(config, vm, lines=100):
|
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)
|
Return console log lines from the API (and display them in a pager in the main CLI)
|
||||||
|
|
|
@ -1166,11 +1166,95 @@ def vm_memory_set(domain, memory, restart):
|
||||||
@click.group(name='network', short_help='Manage attached networks of a virtual machine.', context_settings=CONTEXT_SETTINGS)
|
@click.group(name='network', short_help='Manage attached networks of a virtual machine.', context_settings=CONTEXT_SETTINGS)
|
||||||
def vm_network():
|
def vm_network():
|
||||||
"""
|
"""
|
||||||
Manage the attached networks of a virtual machine in the PVC cluster."
|
Manage the attached networks of a virtual machine in the PVC cluster.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# pvc vm network get
|
||||||
|
###############################################################################
|
||||||
|
@click.command(name='get', short_help='Get the networks of a virtual machine.')
|
||||||
|
@click.argument(
|
||||||
|
'domain'
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-r', '--raw', 'raw', is_flag=True, default=False,
|
||||||
|
help='Display the raw values only without formatting.'
|
||||||
|
)
|
||||||
|
@cluster_req
|
||||||
|
def vm_network_get(domain, raw):
|
||||||
|
"""
|
||||||
|
Get the networks of the virtual machine DOMAIN.
|
||||||
|
"""
|
||||||
|
|
||||||
|
retcode, retdata = pvc_vm.vm_networks_get(config, domain)
|
||||||
|
if not raw:
|
||||||
|
retmsg = pvc_vm.format_vm_networks(config, domain, retdata)
|
||||||
|
else:
|
||||||
|
network_vnis = list()
|
||||||
|
for network in retdata:
|
||||||
|
network_vnis.append(network[0])
|
||||||
|
retmsg = ','.join(network_vnis)
|
||||||
|
cleanup(retcode, retmsg)
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# pvc vm network add
|
||||||
|
###############################################################################
|
||||||
|
@click.command(name='add', short_help='Add network to a virtual machine.')
|
||||||
|
@click.argument(
|
||||||
|
'domain'
|
||||||
|
)
|
||||||
|
@click.argument(
|
||||||
|
'vni'
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-a', '--macaddr', 'macaddr', default=None,
|
||||||
|
help='Use this MAC address instead of random generation; must be a valid MAC address in colon-deliniated format.'
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-m', '--model', 'model', default='virtio',
|
||||||
|
help='The model for the interface; must be a valid libvirt model.'
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
||||||
|
help='Immediately restart VM to apply new config.'
|
||||||
|
)
|
||||||
|
@cluster_req
|
||||||
|
def vm_network_add(domain, vni, macaddr, model, restart):
|
||||||
|
"""
|
||||||
|
Add the network VNI to the virtual machine DOMAIN. Networks are always addded to the end of the current list of networks in the virtual machine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
retcode, retmsg = pvc_vm.vm_networks_add(config, domain, vni, macaddr, model, restart)
|
||||||
|
cleanup(retcode, retmsg)
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# pvc vm network remove
|
||||||
|
###############################################################################
|
||||||
|
@click.command(name='remove', short_help='Remove network from a virtual machine.')
|
||||||
|
@click.argument(
|
||||||
|
'domain'
|
||||||
|
)
|
||||||
|
@click.argument(
|
||||||
|
'vni'
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
'-r', '--restart', 'restart', is_flag=True, default=False,
|
||||||
|
help='Immediately restart VM to apply new config.'
|
||||||
|
)
|
||||||
|
@cluster_req
|
||||||
|
def vm_network_remove(domain, vni, restart):
|
||||||
|
"""
|
||||||
|
Remove the network VNI to the virtual machine DOMAIN.
|
||||||
|
"""
|
||||||
|
|
||||||
|
retcode, retmsg = pvc_vm.vm_networks_remove(config, domain, vni, restart)
|
||||||
|
cleanup(retcode, retmsg)
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# pvc vm volume
|
# pvc vm volume
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
@ -3995,10 +4079,9 @@ vm_vcpu.add_command(vm_vcpu_set)
|
||||||
vm_memory.add_command(vm_memory_get)
|
vm_memory.add_command(vm_memory_get)
|
||||||
vm_memory.add_command(vm_memory_set)
|
vm_memory.add_command(vm_memory_set)
|
||||||
|
|
||||||
# vm_network.add_command(vm_network_list)
|
vm_network.add_command(vm_network_get)
|
||||||
# vm_network.add_command(vm_network_add)
|
vm_network.add_command(vm_network_add)
|
||||||
# vm_network.add_command(vm_network_modify)
|
vm_network.add_command(vm_network_remove)
|
||||||
# vm_network_add_command(vm_network_remove)
|
|
||||||
|
|
||||||
# vm_volume.add_command(vm_volume_list)
|
# vm_volume.add_command(vm_volume_list)
|
||||||
# vm_volume.add_command(vm_volume_add)
|
# vm_volume.add_command(vm_volume_add)
|
||||||
|
|
Loading…
Reference in New Issue