2019-12-25 14:10:23 -05:00
#!/usr/bin/env python3
2019-12-30 09:07:41 -05:00
# vm.py - PVC CLI client function library, VM functions
2019-12-25 14:10:23 -05:00
# Part of the Parallel Virtual Cluster (PVC) system
#
2021-03-25 17:01:55 -04:00
# Copyright (C) 2018-2021 Joshua M. Boniface <joshua@boniface.me>
2019-12-25 14:10:23 -05:00
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
2021-03-25 16:57:17 -04:00
# the Free Software Foundation, version 3.
2019-12-25 14:10:23 -05:00
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
###############################################################################
2019-12-25 19:52:15 -05:00
import time
2019-12-27 09:52:16 -05:00
import re
2019-12-25 14:10:23 -05:00
2019-12-25 19:52:15 -05:00
import cli_lib . ansiprint as ansiprint
2020-06-07 03:04:36 -04:00
from cli_lib . common import call_api , format_bytes , format_metric
2019-12-25 14:10:23 -05:00
2020-11-07 14:45:24 -05:00
2019-12-25 19:52:15 -05:00
#
# Primary functions
#
2019-12-27 09:52:16 -05:00
def vm_info ( config , vm ) :
"""
2020-04-09 10:26:49 -04:00
Get information about ( single ) VM
2019-12-27 09:52:16 -05:00
API endpoint : GET / api / v1 / vm / { vm }
API arguments :
API schema : { json_data_object }
"""
2020-01-08 19:34:24 -05:00
response = call_api ( config , ' get ' , ' /vm/ {vm} ' . format ( vm = vm ) )
2019-12-27 09:52:16 -05:00
if response . status_code == 200 :
2020-12-02 19:15:33 -05:00
if isinstance ( response . json ( ) , list ) and len ( response . json ( ) ) != 1 :
2020-04-09 10:26:49 -04:00
# No exact match; return not found
return False , " VM not found. "
else :
2020-12-02 19:15:33 -05:00
# Return a single instance if the response is a list
2020-04-13 22:56:19 -04:00
if isinstance ( response . json ( ) , list ) :
2020-12-02 19:15:33 -05:00
return True , response . json ( ) [ 0 ]
# This shouldn't happen, but is here just in case
2020-04-13 22:56:19 -04:00
else :
2020-12-02 19:15:33 -05:00
return True , response . json ( )
2019-12-27 09:52:16 -05:00
else :
2020-07-20 12:30:53 -04:00
return False , response . json ( ) . get ( ' message ' , ' ' )
2019-12-27 09:52:16 -05:00
2020-11-07 14:45:24 -05:00
2019-12-27 09:52:16 -05:00
def vm_list ( config , limit , target_node , target_state ) :
"""
2019-12-30 14:35:44 -05:00
Get list information about VMs ( limited by { limit } , { target_node } , or { target_state } )
2019-12-27 09:52:16 -05:00
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
2020-01-08 19:34:24 -05:00
response = call_api ( config , ' get ' , ' /vm ' , params = params )
2019-12-27 09:52:16 -05:00
if response . status_code == 200 :
return True , response . json ( )
else :
2020-07-20 12:30:53 -04:00
return False , response . json ( ) . get ( ' message ' , ' ' )
2019-12-27 09:52:16 -05:00
2020-11-07 14:45:24 -05:00
2020-10-29 11:31:32 -04:00
def vm_define ( config , xml , node , node_limit , node_selector , node_autostart , migration_method ) :
2019-12-27 09:52:16 -05:00
"""
Define a new VM on the cluster
API endpoint : POST / vm
2020-10-29 11:31:32 -04:00
API arguments : xml = { xml } , node = { node } , limit = { node_limit } , selector = { node_selector } , autostart = { node_autostart } , migration_method = { migration_method }
2019-12-27 09:52:16 -05:00
API schema : { " message " : " {data} " }
"""
2020-01-08 19:34:24 -05:00
params = {
' node ' : node ,
' limit ' : node_limit ,
' selector ' : node_selector ,
2020-10-29 11:31:32 -04:00
' autostart ' : node_autostart ,
' migration_method ' : migration_method
2020-01-08 19:34:24 -05:00
}
data = {
' xml ' : xml
}
response = call_api ( config , ' post ' , ' /vm ' , params = params , data = data )
2019-12-27 09:52:16 -05:00
if response . status_code == 200 :
retstatus = True
else :
retstatus = False
2020-07-20 12:30:53 -04:00
return retstatus , response . json ( ) . get ( ' message ' , ' ' )
2019-12-27 09:52:16 -05:00
2020-11-07 14:45:24 -05:00
2019-12-27 09:52:16 -05:00
def vm_modify ( config , vm , xml , restart ) :
"""
Modify the configuration of VM
2020-01-08 09:24:17 -05:00
API endpoint : PUT / vm / { vm }
2019-12-27 09:52:16 -05:00
API arguments : xml = { xml } , restart = { restart }
API schema : { " message " : " {data} " }
"""
2020-01-08 19:34:24 -05:00
params = {
' restart ' : restart
}
data = {
' xml ' : xml
}
response = call_api ( config , ' put ' , ' /vm/ {vm} ' . format ( vm = vm ) , params = params , data = data )
2019-12-27 09:52:16 -05:00
if response . status_code == 200 :
retstatus = True
else :
retstatus = False
2020-07-20 12:30:53 -04:00
return retstatus , response . json ( ) . get ( ' message ' , ' ' )
2019-12-27 09:52:16 -05:00
2020-11-07 14:45:24 -05:00
2020-10-29 11:31:32 -04:00
def vm_metadata ( config , vm , node_limit , node_selector , node_autostart , migration_method , provisioner_profile ) :
2019-12-27 09:52:16 -05:00
"""
Modify PVC metadata of a VM
API endpoint : GET / vm / { vm } / meta , POST / vm / { vm } / meta
2020-10-29 11:31:32 -04:00
API arguments : limit = { node_limit } , selector = { node_selector } , autostart = { node_autostart } , migration_method = { migration_method } profile = { provisioner_profile }
2019-12-27 09:52:16 -05:00
API schema : { " message " : " {data} " }
"""
2020-01-30 11:45:46 -05:00
params = dict ( )
2019-12-27 09:52:16 -05:00
# Update any params that we've sent
if node_limit is not None :
2020-01-30 11:45:46 -05:00
params [ ' limit ' ] = node_limit
2019-12-27 09:52:16 -05:00
if node_selector is not None :
2020-01-30 11:45:46 -05:00
params [ ' selector ' ] = node_selector
2019-12-27 09:52:16 -05:00
if node_autostart is not None :
2020-01-30 11:45:46 -05:00
params [ ' autostart ' ] = node_autostart
2020-10-29 11:31:32 -04:00
if migration_method is not None :
params [ ' migration_method ' ] = migration_method
2020-01-30 11:45:46 -05:00
if provisioner_profile is not None :
params [ ' profile ' ] = provisioner_profile
2019-12-27 09:52:16 -05:00
# Write the new metadata
2020-01-08 19:34:24 -05:00
response = call_api ( config , ' post ' , ' /vm/ {vm} /meta ' . format ( vm = vm ) , params = params )
2019-12-27 09:52:16 -05:00
if response . status_code == 200 :
retstatus = True
else :
retstatus = False
2020-07-20 12:30:53 -04:00
return retstatus , response . json ( ) . get ( ' message ' , ' ' )
2019-12-27 09:52:16 -05:00
2020-11-07 14:45:24 -05:00
2019-12-27 09:52:16 -05:00
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} " }
"""
2020-11-07 12:16:36 -05:00
params = {
2020-01-08 19:34:24 -05:00
' delete_disks ' : delete_disks
}
response = call_api ( config , ' delete ' , ' /vm/ {vm} ' . format ( vm = vm ) , params = params )
2019-12-27 09:52:16 -05:00
if response . status_code == 200 :
retstatus = True
else :
retstatus = False
2020-07-20 12:30:53 -04:00
return retstatus , response . json ( ) . get ( ' message ' , ' ' )
2019-12-27 09:52:16 -05:00
2020-11-07 14:45:24 -05:00
2020-02-19 09:57:31 -05:00
def vm_state ( config , vm , target_state , wait = False ) :
2019-12-27 09:52:16 -05:00
"""
Modify the current state of VM
API endpoint : POST / vm / { vm } / state
2020-02-19 09:57:31 -05:00
API arguments : state = { state } , wait = { wait }
2019-12-27 09:52:16 -05:00
API schema : { " message " : " {data} " }
"""
2020-11-07 12:16:36 -05:00
params = {
2020-01-08 19:34:24 -05:00
' state ' : target_state ,
2020-02-19 09:57:31 -05:00
' wait ' : str ( wait ) . lower ( )
2020-01-08 19:34:24 -05:00
}
response = call_api ( config , ' post ' , ' /vm/ {vm} /state ' . format ( vm = vm ) , params = params )
2019-12-27 09:52:16 -05:00
if response . status_code == 200 :
retstatus = True
else :
retstatus = False
2020-07-20 12:30:53 -04:00
return retstatus , response . json ( ) . get ( ' message ' , ' ' )
2019-12-27 09:52:16 -05:00
2020-11-07 14:45:24 -05:00
2020-06-06 11:49:21 -04:00
def vm_node ( config , vm , target_node , action , force = False , wait = False , force_live = False ) :
2019-12-27 09:52:16 -05:00
"""
Modify the current node of VM via { action }
API endpoint : POST / vm / { vm } / node
2020-06-06 11:49:21 -04:00
API arguments : node = { target_node } , action = { action } , force = { force } , wait = { wait } , force_live = { force_live }
2019-12-27 09:52:16 -05:00
API schema : { " message " : " {data} " }
"""
2020-11-07 12:16:36 -05:00
params = {
2020-01-08 19:34:24 -05:00
' node ' : target_node ,
' action ' : action ,
2020-02-19 09:57:31 -05:00
' force ' : str ( force ) . lower ( ) ,
2020-06-06 11:49:21 -04:00
' wait ' : str ( wait ) . lower ( ) ,
' force_live ' : str ( force_live ) . lower ( )
2020-01-08 19:34:24 -05:00
}
response = call_api ( config , ' post ' , ' /vm/ {vm} /node ' . format ( vm = vm ) , params = params )
2019-12-27 09:52:16 -05:00
if response . status_code == 200 :
retstatus = True
else :
retstatus = False
2020-07-20 12:30:53 -04:00
return retstatus , response . json ( ) . get ( ' message ' , ' ' )
2019-12-27 09:52:16 -05:00
2020-11-07 14:45:24 -05:00
2019-12-27 09:52:16 -05:00
def vm_locks ( config , vm ) :
"""
Flush RBD locks of ( stopped ) VM
API endpoint : POST / vm / { vm } / locks
API arguments :
API schema : { " message " : " {data} " }
"""
2020-01-08 19:34:24 -05:00
response = call_api ( config , ' post ' , ' /vm/ {vm} /locks ' . format ( vm = vm ) )
2019-12-27 09:52:16 -05:00
if response . status_code == 200 :
retstatus = True
else :
retstatus = False
2020-07-20 12:30:53 -04:00
return retstatus , response . json ( ) . get ( ' message ' , ' ' )
2019-12-27 09:52:16 -05:00
2020-11-07 14:45:24 -05:00
2020-11-07 17:35:45 -05:00
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 ,
2020-11-07 22:52:12 -05:00
bold = ' ' ,
end_bold = ' ' ,
2020-11-07 17:35:45 -05:00
name = name ,
vcpus = vcpus [ 0 ] ,
sockets = vcpus [ 1 ] [ 0 ] ,
cores = vcpus [ 1 ] [ 1 ] ,
threads = vcpus [ 1 ] [ 2 ]
)
)
return ' \n ' . join ( output_list )
2020-11-07 18:01:43 -05:00
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 .
2020-11-07 23:06:36 -05:00
Returns an integer memory value .
2020-11-07 18:01:43 -05:00
"""
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 ,
2020-11-07 22:52:12 -05:00
bold = ' ' ,
end_bold = ' ' ,
2020-11-07 18:01:43 -05:00
name = name ,
memory = memory
)
)
return ' \n ' . join ( output_list )
2020-11-07 22:52:12 -05:00
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
2020-11-07 23:02:58 -05:00
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 '
2020-11-07 22:52:12 -05:00
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 ,
2020-11-07 23:02:58 -05:00
bridge = " {} {} " . format ( br_prefix , network ) ,
2020-11-07 22:52:12 -05:00
model = model
)
device_xml = fromstring ( device_string )
last_interface = None
2020-11-10 16:06:43 -05:00
all_interfaces = parsed_xml . devices . find ( ' interface ' )
if all_interfaces is None :
all_interfaces = [ ]
for interface in all_interfaces :
2020-11-07 23:02:58 -05:00
last_interface = re . match ( r ' [vm]*br([0-9a-z]+) ' , interface . source . attrib . get ( ' bridge ' ) ) . group ( 1 )
2020-11-07 22:52:12 -05:00
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 ' ) :
2020-11-07 23:02:58 -05:00
if last_interface == re . match ( r ' [vm]*br([0-9a-z]+) ' , interface . source . attrib . get ( ' bridge ' ) ) . group ( 1 ) :
2020-11-07 22:52:12 -05:00
interface . addnext ( device_xml )
2020-11-10 16:06:43 -05:00
else :
parsed_xml . devices . find ( ' emulator ' ) . addprevious ( device_xml )
2020-11-07 22:52:12 -05:00
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 ' ) :
2020-11-07 23:02:58 -05:00
if_vni = re . match ( r ' [vm]*br([0-9a-z]+) ' , interface . source . attrib . get ( ' bridge ' ) ) . group ( 1 )
2020-11-07 22:52:12 -05:00
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 .
2020-11-07 23:06:36 -05:00
Returns a list of tuples of ( network_vni , mac_address , model )
2020-11-07 22:52:12 -05:00
"""
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 ' )
2020-11-07 23:02:58 -05:00
network = re . match ( r ' [vm]*br([0-9a-z]+) ' , interface . source . attrib . get ( ' bridge ' ) ) . group ( 1 )
2020-11-07 22:52:12 -05:00
network_data . append ( ( network , mac_address , model ) )
return True , network_data
def format_vm_networks ( config , name , networks ) :
"""
2020-11-07 23:06:36 -05:00
Format the output of a network list in a nice table
2020-11-07 22:52:12 -05:00
"""
output_list = [ ]
name_length = 5
2020-11-08 00:48:50 -05:00
vni_length = 8
macaddr_length = 12
model_length = 6
2020-11-07 22:52:12 -05:00
_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 )
2020-11-08 00:48:50 -05:00
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 ( )
2020-11-10 16:06:43 -05:00
all_disks = parsed_xml . devices . find ( ' disk ' )
if all_disks is None :
all_disks = [ ]
for disk in all_disks :
2020-11-08 00:48:50 -05:00
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 )
2020-11-10 16:06:43 -05:00
all_disks = parsed_xml . devices . find ( ' disk ' )
if all_disks is None :
all_disks = [ ]
for disk in all_disks :
2020-11-08 00:48:50 -05:00
last_disk = disk
2020-11-10 16:06:43 -05:00
if last_disk is None :
parsed_xml . devices . find ( ' emulator ' ) . addprevious ( new_disk_details )
2020-11-08 00:48:50 -05:00
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 )
2019-12-25 19:52:15 -05:00
def view_console_log ( config , vm , lines = 100 ) :
"""
2020-01-08 10:06:34 -05:00
Return console log lines from the API ( and display them in a pager in the main CLI )
2019-12-25 19:52:15 -05:00
API endpoint : GET / vm / { vm } / console
API arguments : lines = { lines }
API schema : { " name " : " {vmname} " , " data " : " {console_log} " }
"""
2020-01-08 19:34:24 -05:00
params = {
' lines ' : lines
}
response = call_api ( config , ' get ' , ' /vm/ {vm} /console ' . format ( vm = vm ) , params = params )
2019-12-25 19:52:15 -05:00
2020-01-05 17:07:39 -05:00
if response . status_code != 200 :
2020-07-20 12:30:53 -04:00
return False , response . json ( ) . get ( ' message ' , ' ' )
2020-01-05 17:07:39 -05:00
2019-12-25 19:52:15 -05:00
console_log = response . json ( ) [ ' data ' ]
2019-12-25 14:10:23 -05:00
# Shrink the log buffer to length lines
shrunk_log = console_log . split ( ' \n ' ) [ - lines : ]
loglines = ' \n ' . join ( shrunk_log )
2020-01-08 10:06:34 -05:00
return True , loglines
2019-12-25 14:10:23 -05:00
2020-11-07 14:45:24 -05:00
2019-12-25 19:52:15 -05:00
def follow_console_log ( config , vm , lines = 10 ) :
"""
Return and follow console log lines from the API
API endpoint : GET / vm / { vm } / console
API arguments : lines = { lines }
API schema : { " name " : " {vmname} " , " data " : " {console_log} " }
"""
2021-05-19 16:22:48 -04:00
# We always grab 500 to match the follow call, but only _show_ `lines` number
2020-01-08 19:34:24 -05:00
params = {
2021-05-19 16:22:48 -04:00
' lines ' : 500
2020-01-08 19:34:24 -05:00
}
response = call_api ( config , ' get ' , ' /vm/ {vm} /console ' . format ( vm = vm ) , params = params )
2019-12-25 14:10:23 -05:00
2020-01-15 00:47:14 -05:00
if response . status_code != 200 :
2020-07-20 12:30:53 -04:00
return False , response . json ( ) . get ( ' message ' , ' ' )
2020-01-15 00:47:14 -05:00
2019-12-25 14:10:23 -05:00
# Shrink the log buffer to length lines
2020-01-09 13:53:11 -05:00
console_log = response . json ( ) [ ' data ' ]
2021-05-19 16:22:48 -04:00
shrunk_log = console_log . split ( ' \n ' ) [ - int ( lines ) : ]
2019-12-25 14:10:23 -05:00
loglines = ' \n ' . join ( shrunk_log )
# Print the initial data and begin following
print ( loglines , end = ' ' )
2019-12-25 19:52:15 -05:00
while True :
2020-11-10 16:17:13 -05:00
# Grab the next line set (500 is a reasonable number of lines per second; any more are skipped)
2020-01-30 09:28:47 -05:00
try :
2020-11-10 16:14:33 -05:00
params = {
2020-11-10 16:17:13 -05:00
' lines ' : 500
2020-11-10 16:14:33 -05:00
}
2020-01-30 09:28:47 -05:00
response = call_api ( config , ' get ' , ' /vm/ {vm} /console ' . format ( vm = vm ) , params = params )
new_console_log = response . json ( ) [ ' data ' ]
2020-11-06 18:55:10 -05:00
except Exception :
2020-01-30 09:28:47 -05:00
break
2019-12-25 19:52:15 -05:00
# Split the new and old log strings into constitutent lines
old_console_loglines = console_log . split ( ' \n ' )
new_console_loglines = new_console_log . split ( ' \n ' )
# Set the console log to the new log value for the next iteration
console_log = new_console_log
# Remove the lines from the old log until we hit the first line of the new log; this
# ensures that the old log is a string that we can remove from the new log entirely
for index , line in enumerate ( old_console_loglines , start = 0 ) :
if line == new_console_loglines [ 0 ] :
del old_console_loglines [ 0 : index ]
break
# Rejoin the log lines into strings
old_console_log = ' \n ' . join ( old_console_loglines )
new_console_log = ' \n ' . join ( new_console_loglines )
# Remove the old lines from the new log
diff_console_log = new_console_log . replace ( old_console_log , " " )
# If there's a difference, print it out
if diff_console_log :
print ( diff_console_log , end = ' ' )
# Wait a second
time . sleep ( 1 )
2019-12-25 14:10:23 -05:00
return True , ' '
2020-11-07 14:45:24 -05:00
2019-12-25 19:52:15 -05:00
#
# Output display functions
#
def format_info ( config , domain_information , long_output ) :
2019-12-25 14:10:23 -05:00
# Format a nice output; do this line-by-line then concat the elements at the end
ainformation = [ ]
ainformation . append ( ' {} Virtual machine information: {} ' . format ( ansiprint . bold ( ) , ansiprint . end ( ) ) )
ainformation . append ( ' ' )
# Basic information
ainformation . append ( ' {} UUID: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' uuid ' ] ) )
ainformation . append ( ' {} Name: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' name ' ] ) )
ainformation . append ( ' {} Description: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' description ' ] ) )
ainformation . append ( ' {} Profile: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' profile ' ] ) )
ainformation . append ( ' {} Memory (M): {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' memory ' ] ) )
ainformation . append ( ' {} vCPUs: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' vcpu ' ] ) )
ainformation . append ( ' {} Topology (S/C/T): {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' vcpu_topology ' ] ) )
2020-12-20 16:00:55 -05:00
if domain_information [ ' vnc ' ] . get ( ' listen ' , ' None ' ) != ' None ' and domain_information [ ' vnc ' ] . get ( ' port ' , ' None ' ) != ' None ' :
ainformation . append ( ' ' )
ainformation . append ( ' {} VNC listen: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' vnc ' ] [ ' listen ' ] ) )
ainformation . append ( ' {} VNC port: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' vnc ' ] [ ' port ' ] ) )
2020-11-06 19:35:19 -05:00
if long_output is True :
2019-12-25 14:10:23 -05:00
# Virtualization information
ainformation . append ( ' ' )
ainformation . append ( ' {} Emulator: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' emulator ' ] ) )
ainformation . append ( ' {} Type: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' type ' ] ) )
ainformation . append ( ' {} Arch: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' arch ' ] ) )
ainformation . append ( ' {} Machine: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' machine ' ] ) )
ainformation . append ( ' {} Features: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , ' ' . join ( domain_information [ ' features ' ] ) ) )
2020-06-07 03:04:36 -04:00
ainformation . append ( ' ' )
ainformation . append ( ' {0} Memory stats: {1} {2} Swap In Swap Out Faults (maj/min) Available Usable Unused RSS {3} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , ansiprint . bold ( ) , ansiprint . end ( ) ) )
ainformation . append ( ' {0: <7} {1: <8} {2: <16} {3: <10} {4: <7} {5: <7} {6: <10} ' . format (
2020-11-11 13:13:31 -05:00
format_metric ( domain_information [ ' memory_stats ' ] . get ( ' swap_in ' , 0 ) ) ,
format_metric ( domain_information [ ' memory_stats ' ] . get ( ' swap_out ' , 0 ) ) ,
' / ' . join ( [ format_metric ( domain_information [ ' memory_stats ' ] . get ( ' major_fault ' , 0 ) ) , format_metric ( domain_information [ ' memory_stats ' ] . get ( ' minor_fault ' , 0 ) ) ] ) ,
format_bytes ( domain_information [ ' memory_stats ' ] . get ( ' available ' , 0 ) * 1024 ) ,
format_bytes ( domain_information [ ' memory_stats ' ] . get ( ' usable ' , 0 ) * 1024 ) ,
format_bytes ( domain_information [ ' memory_stats ' ] . get ( ' unused ' , 0 ) * 1024 ) ,
format_bytes ( domain_information [ ' memory_stats ' ] . get ( ' rss ' , 0 ) * 1024 )
2020-06-07 03:04:36 -04:00
) )
ainformation . append ( ' ' )
ainformation . append ( ' {0} vCPU stats: {1} {2} CPU time (ns) User time (ns) System time (ns) {3} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , ansiprint . bold ( ) , ansiprint . end ( ) ) )
ainformation . append ( ' {0: <16} {1: <16} {2: <15} ' . format (
2020-11-11 13:13:31 -05:00
str ( domain_information [ ' vcpu_stats ' ] . get ( ' cpu_time ' , 0 ) ) ,
str ( domain_information [ ' vcpu_stats ' ] . get ( ' user_time ' , 0 ) ) ,
str ( domain_information [ ' vcpu_stats ' ] . get ( ' system_time ' , 0 ) )
2020-06-07 03:04:36 -04:00
) )
2019-12-25 14:10:23 -05:00
# PVC cluster information
ainformation . append ( ' ' )
dstate_colour = {
' start ' : ansiprint . green ( ) ,
' restart ' : ansiprint . yellow ( ) ,
' shutdown ' : ansiprint . yellow ( ) ,
' stop ' : ansiprint . red ( ) ,
' disable ' : ansiprint . blue ( ) ,
' fail ' : ansiprint . red ( ) ,
' migrate ' : ansiprint . blue ( ) ,
2020-01-08 17:40:02 -05:00
' unmigrate ' : ansiprint . blue ( ) ,
' provision ' : ansiprint . blue ( )
2019-12-25 14:10:23 -05:00
}
ainformation . append ( ' {} State: {} {} {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , dstate_colour [ domain_information [ ' state ' ] ] , domain_information [ ' state ' ] , ansiprint . end ( ) ) )
ainformation . append ( ' {} Current Node: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' node ' ] ) )
if not domain_information [ ' last_node ' ] :
domain_information [ ' last_node ' ] = " N/A "
ainformation . append ( ' {} Previous Node: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' last_node ' ] ) )
# Get a failure reason if applicable
if domain_information [ ' failed_reason ' ] :
ainformation . append ( ' ' )
ainformation . append ( ' {} Failure reason: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' failed_reason ' ] ) )
2020-10-29 11:31:32 -04:00
if not domain_information . get ( ' node_selector ' ) :
2019-12-25 14:10:23 -05:00
formatted_node_selector = " False "
else :
formatted_node_selector = domain_information [ ' node_selector ' ]
2020-10-29 11:31:32 -04:00
if not domain_information . get ( ' node_limit ' ) :
2019-12-25 14:10:23 -05:00
formatted_node_limit = " False "
else :
formatted_node_limit = ' , ' . join ( domain_information [ ' node_limit ' ] )
2020-10-29 11:31:32 -04:00
if not domain_information . get ( ' node_autostart ' ) :
2019-12-25 14:10:23 -05:00
formatted_node_autostart = " False "
else :
formatted_node_autostart = domain_information [ ' node_autostart ' ]
2020-10-29 11:31:32 -04:00
if not domain_information . get ( ' migration_method ' ) :
2020-12-03 17:08:49 -05:00
formatted_migration_method = " any "
2020-10-29 11:31:32 -04:00
else :
formatted_migration_method = domain_information [ ' migration_method ' ]
2019-12-25 14:10:23 -05:00
ainformation . append ( ' {} Migration selector: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , formatted_node_selector ) )
ainformation . append ( ' {} Node limit: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , formatted_node_limit ) )
ainformation . append ( ' {} Autostart: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , formatted_node_autostart ) )
2020-10-29 11:31:32 -04:00
ainformation . append ( ' {} Migration Method: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , formatted_migration_method ) )
2019-12-25 14:10:23 -05:00
# Network list
net_list = [ ]
for net in domain_information [ ' networks ' ] :
# Split out just the numerical (VNI) part of the brXXXX name
net_vnis = re . findall ( r ' \ d+ ' , net [ ' source ' ] )
if net_vnis :
net_vni = net_vnis [ 0 ]
else :
net_vni = re . sub ( ' br ' , ' ' , net [ ' source ' ] )
2019-12-27 09:52:16 -05:00
2020-01-08 19:34:24 -05:00
response = call_api ( config , ' get ' , ' /network/ {net} ' . format ( net = net_vni ) )
2020-04-20 10:56:52 -04:00
if response . status_code != 200 and net_vni not in [ ' cluster ' , ' storage ' , ' upstream ' ] :
2019-12-25 14:10:23 -05:00
net_list . append ( ansiprint . red ( ) + net_vni + ansiprint . end ( ) + ' [invalid] ' )
else :
net_list . append ( net_vni )
2019-12-27 09:52:16 -05:00
2019-12-25 14:10:23 -05:00
ainformation . append ( ' ' )
ainformation . append ( ' {} Networks: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , ' , ' . join ( net_list ) ) )
2020-11-06 19:35:19 -05:00
if long_output is True :
2019-12-25 14:10:23 -05:00
# Disk list
ainformation . append ( ' ' )
name_length = 0
for disk in domain_information [ ' disks ' ] :
_name_length = len ( disk [ ' name ' ] ) + 1
if _name_length > name_length :
name_length = _name_length
2020-06-07 03:04:36 -04:00
ainformation . append ( ' {0} Disks: {1} {2} ID Type { 3: < {width} } Dev Bus Requests (r/w) Data (r/w) {4} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , ansiprint . bold ( ) , ' Name ' , ansiprint . end ( ) , width = name_length ) )
2019-12-25 14:10:23 -05:00
for disk in domain_information [ ' disks ' ] :
2020-06-07 03:04:36 -04:00
ainformation . append ( ' {0: <3} {1: <5} { 2: < {width} } {3: <4} {4: <5} {5: <15} {6} ' . format (
domain_information [ ' disks ' ] . index ( disk ) ,
disk [ ' type ' ] ,
disk [ ' name ' ] ,
disk [ ' dev ' ] ,
disk [ ' bus ' ] ,
2020-11-11 13:13:31 -05:00
' / ' . join ( [ str ( format_metric ( disk . get ( ' rd_req ' , 0 ) ) ) , str ( format_metric ( disk . get ( ' wr_req ' , 0 ) ) ) ] ) ,
' / ' . join ( [ str ( format_bytes ( disk . get ( ' rd_bytes ' , 0 ) ) ) , str ( format_bytes ( disk . get ( ' wr_bytes ' , 0 ) ) ) ] ) ,
2020-06-07 03:04:36 -04:00
width = name_length
) )
2019-12-25 14:10:23 -05:00
ainformation . append ( ' ' )
2020-06-07 03:04:36 -04:00
ainformation . append ( ' {} Interfaces: {} {} ID Type Source Model MAC Data (r/w) Packets (r/w) Errors (r/w) {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , ansiprint . bold ( ) , ansiprint . end ( ) ) )
2019-12-25 14:10:23 -05:00
for net in domain_information [ ' networks ' ] :
2020-06-07 03:04:36 -04:00
ainformation . append ( ' {0: <3} {1: <7} {2: <10} {3: <8} {4: <18} {5: <12} {6: <15} {7: <12} ' . format (
domain_information [ ' networks ' ] . index ( net ) ,
net [ ' type ' ] ,
net [ ' source ' ] ,
net [ ' model ' ] ,
net [ ' mac ' ] ,
2020-11-11 13:13:31 -05:00
' / ' . join ( [ str ( format_bytes ( net . get ( ' rd_bytes ' , 0 ) ) ) , str ( format_bytes ( net . get ( ' wr_bytes ' , 0 ) ) ) ] ) ,
' / ' . join ( [ str ( format_metric ( net . get ( ' rd_packets ' , 0 ) ) ) , str ( format_metric ( net . get ( ' wr_packets ' , 0 ) ) ) ] ) ,
' / ' . join ( [ str ( format_metric ( net . get ( ' rd_errors ' , 0 ) ) ) , str ( format_metric ( net . get ( ' wr_errors ' , 0 ) ) ) ] ) ,
2020-06-07 03:04:36 -04:00
) )
2019-12-25 14:10:23 -05:00
# Controller list
ainformation . append ( ' ' )
ainformation . append ( ' {} Controllers: {} {} ID Type Model {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , ansiprint . bold ( ) , ansiprint . end ( ) ) )
for controller in domain_information [ ' controllers ' ] :
2020-11-08 00:48:50 -05:00
ainformation . append ( ' {0: <3} {1: <14} {2: <8} ' . format ( domain_information [ ' controllers ' ] . index ( controller ) , controller [ ' type ' ] , str ( controller [ ' model ' ] ) ) )
2019-12-25 14:10:23 -05:00
# Join it all together
2020-01-05 12:35:00 -05:00
ainformation . append ( ' ' )
return ' \n ' . join ( ainformation )
2019-12-25 14:10:23 -05:00
2020-11-07 14:45:24 -05:00
2019-12-25 19:52:15 -05:00
def format_list ( config , vm_list , raw ) :
2019-12-25 14:10:23 -05:00
# Function to strip the "br" off of nets and return a nicer list
def getNiceNetID ( domain_information ) :
# Network list
net_list = [ ]
for net in domain_information [ ' networks ' ] :
# Split out just the numerical (VNI) part of the brXXXX name
net_vnis = re . findall ( r ' \ d+ ' , net [ ' source ' ] )
if net_vnis :
net_vni = net_vnis [ 0 ]
else :
net_vni = re . sub ( ' br ' , ' ' , net [ ' source ' ] )
net_list . append ( net_vni )
return net_list
# Handle raw mode since it just lists the names
if raw :
2020-01-05 12:35:00 -05:00
ainformation = list ( )
2019-12-25 14:10:23 -05:00
for vm in sorted ( item [ ' name ' ] for item in vm_list ) :
2020-01-05 12:35:00 -05:00
ainformation . append ( vm )
return ' \n ' . join ( ainformation )
2019-12-25 14:10:23 -05:00
vm_list_output = [ ]
# Determine optimal column widths
# Dynamic columns: node_name, node, migrated
vm_name_length = 5
vm_uuid_length = 37
vm_state_length = 6
vm_nets_length = 9
vm_ram_length = 8
vm_vcpu_length = 6
vm_node_length = 8
vm_migrated_length = 10
for domain_information in vm_list :
net_list = getNiceNetID ( domain_information )
# vm_name column
_vm_name_length = len ( domain_information [ ' name ' ] ) + 1
if _vm_name_length > vm_name_length :
vm_name_length = _vm_name_length
# vm_state column
_vm_state_length = len ( domain_information [ ' state ' ] ) + 1
if _vm_state_length > vm_state_length :
vm_state_length = _vm_state_length
# vm_nets column
_vm_nets_length = len ( ' , ' . join ( net_list ) ) + 1
if _vm_nets_length > vm_nets_length :
vm_nets_length = _vm_nets_length
# vm_node column
_vm_node_length = len ( domain_information [ ' node ' ] ) + 1
if _vm_node_length > vm_node_length :
vm_node_length = _vm_node_length
# vm_migrated column
_vm_migrated_length = len ( domain_information [ ' migrated ' ] ) + 1
if _vm_migrated_length > vm_migrated_length :
vm_migrated_length = _vm_migrated_length
# Format the string (header)
vm_list_output . append (
' {bold} { vm_name: < {vm_name_length} } { vm_uuid: < {vm_uuid_length} } \
{ vm_state_colour } { vm_state : < { vm_state_length } } { end_colour } \
{ vm_networks : < { vm_nets_length } } \
{ vm_memory : < { vm_ram_length } } { vm_vcpu : < { vm_vcpu_length } } \
{ vm_node : < { vm_node_length } } \
{ vm_migrated : < { vm_migrated_length } } { end_bold } ' .format(
vm_name_length = vm_name_length ,
vm_uuid_length = vm_uuid_length ,
vm_state_length = vm_state_length ,
vm_nets_length = vm_nets_length ,
vm_ram_length = vm_ram_length ,
vm_vcpu_length = vm_vcpu_length ,
vm_node_length = vm_node_length ,
vm_migrated_length = vm_migrated_length ,
bold = ansiprint . bold ( ) ,
end_bold = ansiprint . end ( ) ,
vm_state_colour = ' ' ,
end_colour = ' ' ,
vm_name = ' Name ' ,
vm_uuid = ' UUID ' ,
vm_state = ' State ' ,
vm_networks = ' Networks ' ,
vm_memory = ' RAM (M) ' ,
vm_vcpu = ' vCPUs ' ,
vm_node = ' Node ' ,
vm_migrated = ' Migrated '
)
)
2020-11-06 19:05:48 -05:00
2019-12-27 09:52:16 -05:00
# Keep track of nets we found to be valid to cut down on duplicate API hits
valid_net_list = [ ]
2019-12-25 14:10:23 -05:00
# Format the string (elements)
for domain_information in vm_list :
if domain_information [ ' state ' ] == ' start ' :
vm_state_colour = ansiprint . green ( )
elif domain_information [ ' state ' ] == ' restart ' :
vm_state_colour = ansiprint . yellow ( )
elif domain_information [ ' state ' ] == ' shutdown ' :
vm_state_colour = ansiprint . yellow ( )
elif domain_information [ ' state ' ] == ' stop ' :
vm_state_colour = ansiprint . red ( )
elif domain_information [ ' state ' ] == ' fail ' :
vm_state_colour = ansiprint . red ( )
else :
vm_state_colour = ansiprint . blue ( )
# Handle colouring for an invalid network config
raw_net_list = getNiceNetID ( domain_information )
net_list = [ ]
vm_net_colour = ' '
for net_vni in raw_net_list :
2020-11-06 20:37:52 -05:00
if net_vni not in valid_net_list :
2020-01-08 19:34:24 -05:00
response = call_api ( config , ' get ' , ' /network/ {net} ' . format ( net = net_vni ) )
2020-04-20 10:56:52 -04:00
if response . status_code != 200 and net_vni not in [ ' cluster ' , ' storage ' , ' upstream ' ] :
2019-12-27 09:52:16 -05:00
vm_net_colour = ansiprint . red ( )
else :
valid_net_list . append ( net_vni )
2019-12-25 14:10:23 -05:00
net_list . append ( net_vni )
vm_list_output . append (
' {bold} { vm_name: < {vm_name_length} } { vm_uuid: < {vm_uuid_length} } \
{ vm_state_colour } { vm_state : < { vm_state_length } } { end_colour } \
{ vm_net_colour } { vm_networks : < { vm_nets_length } } { end_colour } \
{ vm_memory : < { vm_ram_length } } { vm_vcpu : < { vm_vcpu_length } } \
{ vm_node : < { vm_node_length } } \
{ vm_migrated : < { vm_migrated_length } } { end_bold } ' .format(
vm_name_length = vm_name_length ,
vm_uuid_length = vm_uuid_length ,
vm_state_length = vm_state_length ,
vm_nets_length = vm_nets_length ,
vm_ram_length = vm_ram_length ,
vm_vcpu_length = vm_vcpu_length ,
vm_node_length = vm_node_length ,
vm_migrated_length = vm_migrated_length ,
bold = ' ' ,
end_bold = ' ' ,
vm_state_colour = vm_state_colour ,
end_colour = ansiprint . end ( ) ,
vm_name = domain_information [ ' name ' ] ,
vm_uuid = domain_information [ ' uuid ' ] ,
vm_state = domain_information [ ' state ' ] ,
vm_net_colour = vm_net_colour ,
vm_networks = ' , ' . join ( net_list ) ,
vm_memory = domain_information [ ' memory ' ] ,
vm_vcpu = domain_information [ ' vcpu ' ] ,
vm_node = domain_information [ ' node ' ] ,
vm_migrated = domain_information [ ' migrated ' ]
)
)
2020-01-05 12:35:00 -05:00
return ' \n ' . join ( sorted ( vm_list_output ) )