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
#
2020-01-08 19:38:02 -05:00
# Copyright (C) 2018-2020 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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-04-13 22:56:19 -04: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-04-13 22:56:19 -04:00
if isinstance ( response . json ( ) , list ) :
response = response . json ( ) [ 0 ]
else :
response = response . json ( )
return True , response
2019-12-27 09:52:16 -05:00
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
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 ,
bold = ansiprint . bold ( ) ,
end_bold = ansiprint . end ( ) ,
name = name ,
vcpus = vcpus [ 0 ] ,
sockets = vcpus [ 1 ] [ 0 ] ,
cores = vcpus [ 1 ] [ 1 ] ,
threads = vcpus [ 1 ] [ 2 ]
)
)
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} " }
"""
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 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 ' ]
2019-12-25 14:10:23 -05:00
shrunk_log = console_log . split ( ' \n ' ) [ - lines : ]
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 :
# Grab the next line set
2020-01-30 09:28:47 -05:00
try :
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-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 (
format_metric ( domain_information [ ' memory_stats ' ] . get ( ' swap_in ' ) ) ,
format_metric ( domain_information [ ' memory_stats ' ] . get ( ' swap_out ' ) ) ,
' / ' . join ( [ format_metric ( domain_information [ ' memory_stats ' ] . get ( ' major_fault ' ) ) , format_metric ( domain_information [ ' memory_stats ' ] . get ( ' minor_fault ' ) ) ] ) ,
2020-11-07 14:50:57 -05:00
format_bytes ( domain_information [ ' memory_stats ' ] . get ( ' available ' ) * 1024 ) ,
format_bytes ( domain_information [ ' memory_stats ' ] . get ( ' usable ' ) * 1024 ) ,
format_bytes ( domain_information [ ' memory_stats ' ] . get ( ' unused ' ) * 1024 ) ,
format_bytes ( domain_information [ ' memory_stats ' ] . get ( ' rss ' ) * 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 (
str ( domain_information [ ' vcpu_stats ' ] . get ( ' cpu_time ' ) ) ,
str ( domain_information [ ' vcpu_stats ' ] . get ( ' user_time ' ) ) ,
str ( domain_information [ ' vcpu_stats ' ] . get ( ' system_time ' ) )
) )
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-10-29 12:19:07 -04:00
formatted_migration_method = " none "
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 ' ] ,
' / ' . join ( [ str ( format_metric ( disk [ ' rd_req ' ] ) ) , str ( format_metric ( disk [ ' wr_req ' ] ) ) ] ) ,
' / ' . join ( [ str ( format_bytes ( disk [ ' rd_bytes ' ] ) ) , str ( format_bytes ( disk [ ' wr_bytes ' ] ) ) ] ) ,
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 ' ] ,
' / ' . join ( [ str ( format_bytes ( net [ ' rd_bytes ' ] ) ) , str ( format_bytes ( net [ ' wr_bytes ' ] ) ) ] ) ,
' / ' . join ( [ str ( format_metric ( net [ ' rd_packets ' ] ) ) , str ( format_metric ( net [ ' wr_packets ' ] ) ) ] ) ,
' / ' . join ( [ str ( format_metric ( net [ ' rd_errors ' ] ) ) , str ( format_metric ( net [ ' wr_errors ' ] ) ) ] ) ,
) )
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 ' ] :
ainformation . append ( ' {0: <3} {1: <14} {2: <8} ' . format ( domain_information [ ' controllers ' ] . index ( controller ) , controller [ ' type ' ] , controller [ ' model ' ] ) )
# 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 ) :
2020-01-05 11:51:03 -05:00
# Handle single-element lists
if not isinstance ( vm_list , list ) :
2020-11-07 13:02:54 -05:00
vm_list = [ vm_list ]
2020-01-05 11:51:03 -05:00
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 ) )