commit
a4536c31d4
|
@ -5,6 +5,7 @@ pushd $( git rev-parse --show-toplevel ) &>/dev/null
|
|||
ex=0
|
||||
|
||||
# Linting
|
||||
echo -n "Linting... "
|
||||
./lint
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Aborting commit due to linting errors."
|
||||
|
|
|
@ -119,7 +119,7 @@ devices_disk_footer = """ </source>
|
|||
|
||||
# vhostmd virtualization passthrough device
|
||||
devices_vhostmd = """ <disk type='file' device='disk'>
|
||||
<drive name='qemu' type='raw'/>
|
||||
<driver name='qemu' type='raw'/>
|
||||
<source file='/dev/shm/vhostmd0'/>
|
||||
<target dev='sdz' bus='usb'/>
|
||||
<readonly/>
|
||||
|
|
|
@ -254,6 +254,709 @@ 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='',
|
||||
end_bold='',
|
||||
name=name,
|
||||
vcpus=vcpus[0],
|
||||
sockets=vcpus[1][0],
|
||||
cores=vcpus[1][1],
|
||||
threads=vcpus[1][2]
|
||||
)
|
||||
)
|
||||
return '\n'.join(output_list)
|
||||
|
||||
|
||||
def vm_memory_set(config, vm, memory, restart):
|
||||
"""
|
||||
Set the provisioned memory 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.memory._setText(str(memory))
|
||||
|
||||
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_memory_get(config, vm):
|
||||
"""
|
||||
Get the provisioned memory of the VM
|
||||
|
||||
Calls vm_info to get VM XML.
|
||||
|
||||
Returns an integer memory value.
|
||||
"""
|
||||
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_memory = int(parsed_xml.memory.text)
|
||||
|
||||
return True, vm_memory
|
||||
|
||||
|
||||
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}{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}{name: <{name_length}} \
|
||||
{memory: <{memory_length}}{end_bold}'.format(
|
||||
name_length=name_length,
|
||||
memory_length=memory_length,
|
||||
bold='',
|
||||
end_bold='',
|
||||
name=name,
|
||||
memory=memory
|
||||
)
|
||||
)
|
||||
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
|
||||
import cli_lib.network as pvc_network
|
||||
|
||||
# Verify that the provided network is valid
|
||||
retcode, retdata = pvc_network.net_info(config, network)
|
||||
if not retcode:
|
||||
# Ignore the three special networks
|
||||
if network not in ['upstream', 'cluster', 'storage']:
|
||||
return False, "Network {} is not present in the cluster.".format(network)
|
||||
|
||||
if network in ['upstream', 'cluster', 'storage']:
|
||||
br_prefix = 'br'
|
||||
else:
|
||||
br_prefix = 'vmbr'
|
||||
|
||||
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="{}{}".format(br_prefix, network),
|
||||
model=model
|
||||
)
|
||||
device_xml = fromstring(device_string)
|
||||
|
||||
last_interface = None
|
||||
for interface in parsed_xml.devices.find('interface'):
|
||||
last_interface = re.match(r'[vm]*br([0-9a-z]+)', 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'[vm]*br([0-9a-z]+)', 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'[vm]*br([0-9a-z]+)', 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 list of tuples of (network_vni, mac_address, model)
|
||||
"""
|
||||
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'[vm]*br([0-9a-z]+)', 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 list in a nice table
|
||||
"""
|
||||
output_list = []
|
||||
|
||||
name_length = 5
|
||||
vni_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 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[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 vm_volumes_add(config, vm, volume, disk_id, bus, disk_type, restart):
|
||||
"""
|
||||
Add a new volume 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 copy import deepcopy
|
||||
import cli_lib.ceph as pvc_ceph
|
||||
|
||||
if disk_type == 'rbd':
|
||||
# Verify that the provided volume is valid
|
||||
vpool = volume.split('/')[0]
|
||||
vname = volume.split('/')[1]
|
||||
retcode, retdata = pvc_ceph.ceph_volume_info(config, vpool, vname)
|
||||
if not retcode:
|
||||
return False, "Volume {} is not present in the cluster.".format(volume)
|
||||
|
||||
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.'
|
||||
|
||||
last_disk = None
|
||||
id_list = list()
|
||||
for disk in parsed_xml.devices.find('disk'):
|
||||
id_list.append(disk.target.attrib.get('dev'))
|
||||
if disk.source.attrib.get('protocol') == disk_type:
|
||||
if disk_type == 'rbd':
|
||||
last_disk = disk.source.attrib.get('name')
|
||||
elif disk_type == 'file':
|
||||
last_disk = disk.source.attrib.get('file')
|
||||
if last_disk == volume:
|
||||
return False, 'Volume {} is already configured for VM {}.'.format(volume, vm)
|
||||
last_disk_details = deepcopy(disk)
|
||||
|
||||
if disk_id is not None:
|
||||
if disk_id in id_list:
|
||||
return False, 'Manually specified disk ID {} is already in use for VM {}.'.format(disk_id, vm)
|
||||
else:
|
||||
# Find the next free disk ID
|
||||
first_dev_prefix = id_list[0][0:-1]
|
||||
|
||||
for char in range(ord('a'), ord('z')):
|
||||
char = chr(char)
|
||||
next_id = "{}{}".format(first_dev_prefix, char)
|
||||
if next_id not in id_list:
|
||||
break
|
||||
else:
|
||||
next_id = None
|
||||
if next_id is None:
|
||||
return False, 'Failed to find a valid disk_id and none specified; too many disks for VM {}?'.format(vm)
|
||||
disk_id = next_id
|
||||
|
||||
if last_disk is None:
|
||||
if disk_type == 'rbd':
|
||||
# RBD volumes need an example to be based on
|
||||
return False, "There are no existing RBD volumes attached to this VM. Autoconfiguration failed; use the 'vm modify' command to manually configure this volume with the required details for authentication, hosts, etc.."
|
||||
elif disk_type == 'file':
|
||||
# File types can be added ad-hoc
|
||||
disk_template = '<disk type="file" device="disk"><driver name="qemu" type="raw"/><source file="{source}"/><target dev="{dev}" bus="{bus}"/></disk>'.format(
|
||||
source=volume,
|
||||
dev=disk_id,
|
||||
bus=bus
|
||||
)
|
||||
last_disk_details = fromstring(disk_template)
|
||||
|
||||
new_disk_details = last_disk_details
|
||||
new_disk_details.target.set('dev', disk_id)
|
||||
new_disk_details.target.set('bus', bus)
|
||||
if disk_type == 'rbd':
|
||||
new_disk_details.source.set('name', volume)
|
||||
elif disk_type == 'file':
|
||||
new_disk_details.source.set('file', volume)
|
||||
|
||||
for disk in parsed_xml.devices.find('disk'):
|
||||
last_disk = disk
|
||||
last_disk.addnext(new_disk_details)
|
||||
|
||||
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_volumes_remove(config, vm, volume, restart):
|
||||
"""
|
||||
Remove a volume 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 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:
|
||||
disk.getparent().remove(disk)
|
||||
|
||||
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_volumes_get(config, vm):
|
||||
"""
|
||||
Get the volumes of the VM
|
||||
|
||||
Calls vm_info to get VM XML.
|
||||
|
||||
Returns a list of tuples of (volume, disk_id, type, bus)
|
||||
"""
|
||||
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.'
|
||||
|
||||
volume_data = list()
|
||||
for disk in parsed_xml.devices.find('disk'):
|
||||
protocol = disk.attrib.get('type')
|
||||
disk_id = disk.target.attrib.get('dev')
|
||||
bus = disk.target.attrib.get('bus')
|
||||
if protocol == 'network':
|
||||
protocol = disk.source.attrib.get('protocol')
|
||||
source = disk.source.attrib.get('name')
|
||||
elif protocol == 'file':
|
||||
protocol = 'file'
|
||||
source = disk.source.attrib.get('file')
|
||||
else:
|
||||
protocol = 'unknown'
|
||||
source = 'unknown'
|
||||
|
||||
volume_data.append((source, disk_id, protocol, bus))
|
||||
|
||||
return True, volume_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
|
||||
|
||||
_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[1]) + 1
|
||||
if _disk_id_length > disk_id_length:
|
||||
disk_id_length = _disk_id_length
|
||||
|
||||
_protocol_length = len(volume[2]) + 1
|
||||
if _protocol_length > protocol_length:
|
||||
protocol_length = _protocol_length
|
||||
|
||||
_bus_length = len(volume[3]) + 1
|
||||
if _bus_length > bus_length:
|
||||
bus_length = _bus_length
|
||||
|
||||
output_list.append(
|
||||
'{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',
|
||||
bus='Bus'
|
||||
)
|
||||
)
|
||||
count = 0
|
||||
for volume in volumes:
|
||||
if count > 0:
|
||||
name = ''
|
||||
count += 1
|
||||
output_list.append(
|
||||
'{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='',
|
||||
name=name,
|
||||
volume=volume[0],
|
||||
disk_id=volume[1],
|
||||
protocol=volume[2],
|
||||
bus=volume[3]
|
||||
)
|
||||
)
|
||||
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)
|
||||
|
@ -484,7 +1187,7 @@ def format_info(config, domain_information, long_output):
|
|||
ainformation.append('')
|
||||
ainformation.append('{}Controllers:{} {}ID Type Model{}'.format(ansiprint.purple(), ansiprint.end(), ansiprint.bold(), ansiprint.end()))
|
||||
for controller in domain_information['controllers']:
|
||||
ainformation.append(' {0: <3} {1: <14} {2: <8}'.format(domain_information['controllers'].index(controller), controller['type'], controller['model']))
|
||||
ainformation.append(' {0: <3} {1: <14} {2: <8}'.format(domain_information['controllers'].index(controller), controller['type'], str(controller['model'])))
|
||||
|
||||
# Join it all together
|
||||
ainformation.append('')
|
||||
|
|
|
@ -1025,6 +1025,343 @@ def vm_flush_locks(domain):
|
|||
cleanup(retcode, retmsg)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm vcpu
|
||||
###############################################################################
|
||||
@click.group(name='vcpu', short_help='Manage vCPU counts of a virtual machine.', context_settings=CONTEXT_SETTINGS)
|
||||
def vm_vcpu():
|
||||
"""
|
||||
Manage the vCPU counts of a virtual machine in the PVC cluster."
|
||||
"""
|
||||
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 <sockets>,<cores>,<threads>. 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
|
||||
###############################################################################
|
||||
@click.group(name='memory', short_help='Manage provisioned memory of a virtual machine.', context_settings=CONTEXT_SETTINGS)
|
||||
def vm_memory():
|
||||
"""
|
||||
Manage the provisioned memory of a virtual machine in the PVC cluster."
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm memory get
|
||||
###############################################################################
|
||||
@click.command(name='get', short_help='Get the current provisioned memory 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_memory_get(domain, raw):
|
||||
"""
|
||||
Get the current provisioned memory of the virtual machine DOMAIN.
|
||||
"""
|
||||
|
||||
retcode, retmsg = pvc_vm.vm_memory_get(config, domain)
|
||||
if not raw:
|
||||
retmsg = pvc_vm.format_vm_memory(config, domain, retmsg)
|
||||
cleanup(retcode, retmsg)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm memory set
|
||||
###############################################################################
|
||||
@click.command(name='set', short_help='Set the provisioned memory of a virtual machine.')
|
||||
@click.argument(
|
||||
'domain'
|
||||
)
|
||||
@click.argument(
|
||||
'memory'
|
||||
)
|
||||
@click.option(
|
||||
'-r', '--restart', 'restart', is_flag=True, default=False,
|
||||
help='Immediately restart VM to apply new config.'
|
||||
)
|
||||
@cluster_req
|
||||
def vm_memory_set(domain, memory, restart):
|
||||
"""
|
||||
Set the provisioned memory of the virtual machine DOMAIN to MEMORY; MEMORY must be an integer in MB.
|
||||
"""
|
||||
|
||||
retcode, retmsg = pvc_vm.vm_memory_set(config, domain, memory, restart)
|
||||
cleanup(retcode, retmsg)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm network
|
||||
###############################################################################
|
||||
@click.group(name='network', short_help='Manage attached networks of a virtual machine.', context_settings=CONTEXT_SETTINGS)
|
||||
def vm_network():
|
||||
"""
|
||||
Manage the attached networks of a virtual machine in the PVC cluster.
|
||||
|
||||
Network details cannot be modified here. To modify a network, first remove it, then readd it with the correct settings. Unless the '-r'/'--reboot' flag is provided, this will not affect the running VM until it is restarted.
|
||||
"""
|
||||
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
|
||||
###############################################################################
|
||||
@click.group(name='volume', short_help='Manage attached volumes of a virtual machine.', context_settings=CONTEXT_SETTINGS)
|
||||
def vm_volume():
|
||||
"""
|
||||
Manage the attached volumes of a virtual machine in the PVC cluster.
|
||||
|
||||
Volume details cannot be modified here. To modify a volume, first remove it, then readd it with the correct settings. Unless the '-r'/'--reboot' flag is provided, this will not affect the running VM until it is restarted.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm volume get
|
||||
###############################################################################
|
||||
@click.command(name='get', short_help='Get the volumes 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_volume_get(domain, raw):
|
||||
"""
|
||||
Get the volumes of the virtual machine DOMAIN.
|
||||
"""
|
||||
|
||||
retcode, retdata = pvc_vm.vm_volumes_get(config, domain)
|
||||
if not raw:
|
||||
retmsg = pvc_vm.format_vm_volumes(config, domain, retdata)
|
||||
else:
|
||||
volume_paths = list()
|
||||
for volume in retdata:
|
||||
volume_paths.append("{}:{}".format(volume[2], volume[0]))
|
||||
retmsg = ','.join(volume_paths)
|
||||
cleanup(retcode, retmsg)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm volume add
|
||||
###############################################################################
|
||||
@click.command(name='add', short_help='Add volume to a virtual machine.')
|
||||
@click.argument(
|
||||
'domain'
|
||||
)
|
||||
@click.argument(
|
||||
'volume'
|
||||
)
|
||||
@click.option(
|
||||
'-d', '--disk-id', 'disk_id', default=None,
|
||||
help='The disk ID in sdX/vdX/hdX format; if not specified, the next available will be used.'
|
||||
)
|
||||
@click.option(
|
||||
'-b', '--bus', 'bus', default='scsi', show_default=True,
|
||||
type=click.Choice(['scsi', 'ide', 'usb', 'virtio']),
|
||||
help='The bus to attach the disk to; must be present in the VM.'
|
||||
)
|
||||
@click.option(
|
||||
'-t', '--type', 'disk_type', default='rbd', show_default=True,
|
||||
type=click.Choice(['rbd', 'file']),
|
||||
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.'
|
||||
)
|
||||
@cluster_req
|
||||
def vm_volume_add(domain, volume, disk_id, bus, disk_type, restart):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
retcode, retmsg = pvc_vm.vm_volumes_add(config, domain, volume, disk_id, bus, disk_type, restart)
|
||||
cleanup(retcode, retmsg)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm volume remove
|
||||
###############################################################################
|
||||
@click.command(name='remove', short_help='Remove volume 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_volume_remove(domain, vni, restart):
|
||||
"""
|
||||
Remove the volume VNI to the virtual machine DOMAIN.
|
||||
"""
|
||||
|
||||
retcode, retmsg = pvc_vm.vm_volumes_remove(config, domain, vni, restart)
|
||||
cleanup(retcode, retmsg)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# pvc vm log
|
||||
###############################################################################
|
||||
|
@ -3832,6 +4169,20 @@ 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_memory.add_command(vm_memory_get)
|
||||
vm_memory.add_command(vm_memory_set)
|
||||
|
||||
vm_network.add_command(vm_network_get)
|
||||
vm_network.add_command(vm_network_add)
|
||||
vm_network.add_command(vm_network_remove)
|
||||
|
||||
vm_volume.add_command(vm_volume_get)
|
||||
vm_volume.add_command(vm_volume_add)
|
||||
vm_volume.add_command(vm_volume_remove)
|
||||
|
||||
cli_vm.add_command(vm_define)
|
||||
cli_vm.add_command(vm_meta)
|
||||
cli_vm.add_command(vm_modify)
|
||||
|
@ -3847,6 +4198,10 @@ cli_vm.add_command(vm_move)
|
|||
cli_vm.add_command(vm_migrate)
|
||||
cli_vm.add_command(vm_unmigrate)
|
||||
cli_vm.add_command(vm_flush_locks)
|
||||
cli_vm.add_command(vm_vcpu)
|
||||
cli_vm.add_command(vm_memory)
|
||||
cli_vm.add_command(vm_network)
|
||||
cli_vm.add_command(vm_volume)
|
||||
cli_vm.add_command(vm_info)
|
||||
cli_vm.add_command(vm_log)
|
||||
cli_vm.add_command(vm_list)
|
||||
|
|
|
@ -285,7 +285,7 @@ def modify_vm(zk_conn, domain, restart, new_vm_config):
|
|||
zkhandler.writedata(zk_conn, {'/domains/{}/state'.format(dom_uuid): 'restart'})
|
||||
lock.release()
|
||||
|
||||
return True, ''
|
||||
return True, 'Successfully modified configuration of VM "{}".'.format(domain)
|
||||
|
||||
|
||||
def dump_vm(zk_conn, domain):
|
||||
|
|
Loading…
Reference in New Issue