2018-09-20 03:25:58 -04:00
#!/usr/bin/env python3
# vm.py - PVC client function library, VM fuctions
# Part of the Parallel Virtual Cluster (PVC) system
#
# Copyright (C) 2018 Joshua M. Boniface <joshua@boniface.me>
#
# 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/>.
#
###############################################################################
import os
import socket
import time
import uuid
import re
import subprocess
import difflib
import colorama
import click
import lxml . objectify
import configparser
import kazoo . client
2019-04-11 19:06:06 -04:00
from collections import deque
2018-10-20 15:28:25 -04:00
import client_lib . ansiprint as ansiprint
2018-09-25 02:20:32 -04:00
import client_lib . zkhandler as zkhandler
2018-09-20 03:42:40 -04:00
import client_lib . common as common
2018-09-20 03:25:58 -04:00
#
# XML information parsing functions
#
2019-05-20 22:15:28 -04:00
def getInformationFromXML ( zk_conn , uuid ) :
"""
Gather information about a VM from the Libvirt XML configuration in the Zookeper database
and return a dict ( ) containing it .
"""
domain_state = zkhandler . readdata ( zk_conn , ' /domains/ {} /state ' . format ( uuid ) )
domain_node = zkhandler . readdata ( zk_conn , ' /domains/ {} /node ' . format ( uuid ) )
domain_lastnode = zkhandler . readdata ( zk_conn , ' /domains/ {} /lastnode ' . format ( uuid ) )
domain_failedreason = zkhandler . readdata ( zk_conn , ' /domains/ {} /failedreason ' . format ( uuid ) )
if domain_lastnode == ' ' :
domain_lastnode = ' N/A '
2018-09-20 03:25:58 -04:00
2018-10-14 02:01:35 -04:00
parsed_xml = common . getDomainXML ( zk_conn , uuid )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
domain_uuid , domain_name , domain_description , domain_memory , domain_vcpu , domain_vcputopo = common . getDomainMainDetails ( parsed_xml )
domain_networks = common . getDomainNetworks ( parsed_xml )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
domain_type , domain_arch , domain_machine , domain_console , domain_emulator = common . getDomainExtraDetails ( parsed_xml )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
domain_features = common . getDomainCPUFeatures ( parsed_xml )
domain_disks = common . getDomainDisks ( parsed_xml )
domain_controllers = common . getDomainControllers ( parsed_xml )
if domain_lastnode != ' ' :
domain_migrated = ' from {} ' . format ( domain_lastnode )
else :
domain_migrated = ' no '
domain_information = {
' name ' : domain_name ,
' uuid ' : domain_uuid ,
' state ' : domain_state ,
' node ' : domain_node ,
' last_node ' : domain_lastnode ,
' migrated ' : domain_migrated ,
' failed_reason ' : domain_failedreason ,
' description ' : domain_description ,
' memory ' : domain_memory ,
' vcpu ' : domain_vcpu ,
' vcpu_topology ' : domain_vcputopo ,
' networks ' : domain_networks ,
' type ' : domain_type ,
' arch ' : domain_arch ,
' machine ' : domain_machine ,
' console ' : domain_console ,
' emulator ' : domain_emulator ,
' features ' : domain_features ,
' disks ' : domain_disks ,
' controllers ' : domain_controllers
2018-09-20 03:25:58 -04:00
}
2018-10-14 02:01:35 -04:00
2019-05-20 22:15:28 -04:00
return domain_information
2018-09-20 03:25:58 -04:00
#
# Cluster search functions
#
def getClusterDomainList ( zk_conn ) :
# Get a list of UUIDs by listing the children of /domains
2018-10-27 15:27:08 -04:00
uuid_list = zkhandler . listchildren ( zk_conn , ' /domains ' )
2018-09-20 03:25:58 -04:00
name_list = [ ]
# For each UUID, get the corresponding name from the data
for uuid in uuid_list :
2018-10-27 15:27:08 -04:00
name_list . append ( zkhandler . readdata ( zk_conn , ' /domains/ %s ' % uuid ) )
2018-09-20 03:25:58 -04:00
return uuid_list , name_list
def searchClusterByUUID ( zk_conn , uuid ) :
try :
# Get the lists
uuid_list , name_list = getClusterDomainList ( zk_conn )
# We're looking for UUID, so find that element ID
index = uuid_list . index ( uuid )
# Get the name_list element at that index
name = name_list [ index ]
except ValueError :
# We didn't find anything
return None
return name
def searchClusterByName ( zk_conn , name ) :
try :
# Get the lists
uuid_list , name_list = getClusterDomainList ( zk_conn )
# We're looking for name, so find that element ID
index = name_list . index ( name )
# Get the uuid_list element at that index
uuid = uuid_list [ index ]
except ValueError :
# We didn't find anything
return None
return uuid
def getDomainUUID ( zk_conn , domain ) :
# Validate and obtain alternate passed value
if common . validateUUID ( domain ) :
dom_name = searchClusterByUUID ( zk_conn , domain )
dom_uuid = searchClusterByName ( zk_conn , dom_name )
else :
dom_uuid = searchClusterByName ( zk_conn , domain )
dom_name = searchClusterByUUID ( zk_conn , dom_uuid )
return dom_uuid
2018-09-25 02:26:37 -04:00
def getDomainName ( zk_conn , domain ) :
# Validate and obtain alternate passed value
if common . validateUUID ( domain ) :
dom_name = searchClusterByUUID ( zk_conn , domain )
dom_uuid = searchClusterByName ( zk_conn , dom_name )
else :
dom_uuid = searchClusterByName ( zk_conn , domain )
dom_name = searchClusterByUUID ( zk_conn , dom_uuid )
return dom_name
2018-09-20 03:25:58 -04:00
#
# Direct functions
#
2018-10-14 02:01:35 -04:00
def define_vm ( zk_conn , config_data , target_node , selector ) :
2018-09-20 03:25:58 -04:00
# Parse the XML data
2018-09-20 03:46:05 -04:00
parsed_xml = lxml . objectify . fromstring ( config_data )
2018-09-20 03:25:58 -04:00
dom_uuid = parsed_xml . uuid . text
dom_name = parsed_xml . name . text
2018-10-14 02:01:35 -04:00
if target_node == None :
target_node = common . findTargetNode ( zk_conn , selector , dom_uuid )
2018-09-20 03:25:58 -04:00
# Verify node is valid
2018-10-14 02:01:35 -04:00
common . verifyNode ( zk_conn , target_node )
2018-09-20 03:25:58 -04:00
# Add the new domain to Zookeeper
2018-10-27 15:27:08 -04:00
zkhandler . writedata ( zk_conn , {
' /domains/ {} ' . format ( dom_uuid ) : dom_name ,
' /domains/ {} /state ' . format ( dom_uuid ) : ' stop ' ,
' /domains/ {} /node ' . format ( dom_uuid ) : target_node ,
' /domains/ {} /lastnode ' . format ( dom_uuid ) : ' ' ,
' /domains/ {} /failedreason ' . format ( dom_uuid ) : ' ' ,
2019-04-11 19:06:06 -04:00
' /domains/ {} /consolelog ' . format ( dom_uuid ) : ' ' ,
2018-10-27 15:27:08 -04:00
' /domains/ {} /xml ' . format ( dom_uuid ) : config_data
} )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
return True , ' Added new VM with Name " {} " and UUID " {} " to database. ' . format ( dom_name , dom_uuid )
2018-09-20 03:25:58 -04:00
2018-09-25 02:32:08 -04:00
def modify_vm ( zk_conn , domain , restart , new_vm_config ) :
2018-09-20 03:25:58 -04:00
dom_uuid = getDomainUUID ( zk_conn , domain )
2019-04-11 19:06:06 -04:00
if not dom_uuid :
2018-09-20 03:25:58 -04:00
return False , ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain )
2018-09-25 02:32:08 -04:00
dom_name = getDomainName ( zk_conn , domain )
2018-09-20 03:25:58 -04:00
# Add the modified config to Zookeeper
2018-10-27 15:27:08 -04:00
zk_data = {
' /domains/ {} ' . format ( dom_uuid ) : dom_name ,
' /domains/ {} /xml ' . format ( dom_uuid ) : new_vm_config
}
2018-09-20 03:25:58 -04:00
if restart == True :
2018-10-27 15:27:08 -04:00
zk_data . update ( { ' /domains/ {} /state ' . format ( dom_uuid ) : ' restart ' } )
zkhandler . writedata ( zk_conn , zk_data )
2018-09-20 03:25:58 -04:00
return True , ' '
2019-03-12 21:09:54 -04:00
def dump_vm ( zk_conn , domain ) :
dom_uuid = getDomainUUID ( zk_conn , domain )
2019-04-11 19:06:06 -04:00
if not dom_uuid :
2019-03-12 21:09:54 -04:00
return False , ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain )
# Gram the domain XML and dump it to stdout
vm_xml = zkhandler . readdata ( zk_conn , ' /domains/ {} /xml ' . format ( dom_uuid ) )
2019-05-20 22:15:28 -04:00
return True , vm_xml
2019-03-12 21:09:54 -04:00
2019-05-20 22:15:28 -04:00
def undefine_vm ( zk_conn , domain , is_cli = False ) :
2018-09-20 03:25:58 -04:00
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID ( zk_conn , domain )
2019-04-11 19:06:06 -04:00
if not dom_uuid :
2018-09-20 03:25:58 -04:00
common . stopZKConnection ( zk_conn )
return False , ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain )
# Shut down the VM
try :
2018-10-27 15:27:08 -04:00
current_vm_state = zkhandler . readdata ( zk_conn , ' /domains/ {} /state ' . format ( dom_uuid ) )
2018-09-20 03:25:58 -04:00
if current_vm_state != ' stop ' :
2019-05-20 22:15:28 -04:00
if is_cli :
click . echo ( ' Forcibly stopping VM " {} " . ' . format ( dom_uuid ) )
2018-09-20 03:25:58 -04:00
# Set the domain into stop mode
2018-10-27 15:27:08 -04:00
zkhandler . writedata ( zk_conn , { ' /domains/ {} /state ' . format ( dom_uuid ) : ' stop ' } )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
# Wait for 1 second to allow state to flow to all nodes
if is_cli :
click . echo ( ' Waiting for cluster to update. ' )
time . sleep ( 2 )
2018-09-20 03:25:58 -04:00
except :
pass
# Gracefully terminate the class instances
try :
2019-05-20 22:15:28 -04:00
if is_cli :
click . echo ( ' Deleting VM " {} " from nodes. ' . format ( dom_uuid ) )
2018-10-27 15:27:08 -04:00
zkhandler . writedata ( zk_conn , { ' /domains/ {} /state ' . format ( dom_uuid ) : ' delete ' } )
2019-05-20 22:15:28 -04:00
time . sleep ( 2 )
2018-09-20 03:25:58 -04:00
except :
pass
# Delete the configurations
try :
2019-05-20 22:15:28 -04:00
if is_cli :
click . echo ( ' Undefining VM " {} " . ' . format ( dom_uuid ) )
2019-03-15 01:13:03 -04:00
zkhandler . deletekey ( zk_conn , ' /domains/ {} ' . format ( dom_uuid ) )
2018-09-20 03:25:58 -04:00
except :
pass
2019-05-20 22:15:28 -04:00
return True , ' Removed VM " {} " from the cluster. ' . format ( dom_uuid )
2018-09-20 03:25:58 -04:00
def start_vm ( zk_conn , domain ) :
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID ( zk_conn , domain )
2019-04-11 19:06:06 -04:00
if not dom_uuid :
2018-09-20 03:25:58 -04:00
common . stopZKConnection ( zk_conn )
return False , ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain )
# Set the VM to start
2018-10-27 15:27:08 -04:00
zkhandler . writedata ( zk_conn , { ' /domains/ {} /state ' . format ( dom_uuid ) : ' start ' } )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
return True , ' Starting VM " {} " . ' . format ( dom_uuid )
2018-09-20 03:25:58 -04:00
def restart_vm ( zk_conn , domain ) :
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID ( zk_conn , domain )
2019-04-11 19:06:06 -04:00
if not dom_uuid :
2018-09-20 03:25:58 -04:00
common . stopZKConnection ( zk_conn )
return False , ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain )
# Get state and verify we're OK to proceed
2018-10-27 15:27:08 -04:00
current_state = zkhandler . readdata ( zk_conn , ' /domains/ {} /state ' . format ( dom_uuid ) )
2018-09-20 03:25:58 -04:00
if current_state != ' start ' :
common . stopZKConnection ( zk_conn )
return False , ' ERROR: VM " {} " is not in " start " state! ' . format ( dom_uuid )
# Set the VM to start
2018-10-27 15:27:08 -04:00
zkhandler . writedata ( zk_conn , { ' /domains/ {} /state ' . format ( dom_uuid ) : ' restart ' } )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
return True , ' Restarting VM " {} " . ' . format ( dom_uuid )
2018-09-20 03:25:58 -04:00
def shutdown_vm ( zk_conn , domain ) :
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID ( zk_conn , domain )
2019-04-11 19:06:06 -04:00
if not dom_uuid :
2018-09-20 03:25:58 -04:00
return False , ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain )
# Get state and verify we're OK to proceed
2018-10-27 15:27:08 -04:00
current_state = zkhandler . readdata ( zk_conn , ' /domains/ {} /state ' . format ( dom_uuid ) )
2018-09-20 03:25:58 -04:00
if current_state != ' start ' :
common . stopZKConnection ( zk_conn )
return False , ' ERROR: VM " {} " is not in " start " state! ' . format ( dom_uuid )
# Set the VM to shutdown
2018-10-27 15:27:08 -04:00
zkhandler . writedata ( zk_conn , { ' /domains/ {} /state ' . format ( dom_uuid ) : ' shutdown ' } )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
return True , ' Shutting down VM " {} " . ' . format ( dom_uuid )
2018-09-20 03:25:58 -04:00
def stop_vm ( zk_conn , domain ) :
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID ( zk_conn , domain )
2019-04-11 19:06:06 -04:00
if not dom_uuid :
2018-09-20 03:25:58 -04:00
common . stopZKConnection ( zk_conn )
return False , ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain )
# Get state and verify we're OK to proceed
2018-10-27 15:27:08 -04:00
current_state = zkhandler . readdata ( zk_conn , ' /domains/ {} /state ' . format ( dom_uuid ) )
2018-09-20 03:25:58 -04:00
# Set the VM to start
2018-10-27 15:27:08 -04:00
zkhandler . writedata ( zk_conn , { ' /domains/ {} /state ' . format ( dom_uuid ) : ' stop ' } )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
return True , ' Forcibly stopping VM " {} " . ' . format ( dom_uuid )
2018-09-20 03:25:58 -04:00
2018-10-14 02:01:35 -04:00
def move_vm ( zk_conn , domain , target_node , selector ) :
2018-09-20 03:25:58 -04:00
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID ( zk_conn , domain )
2019-04-11 19:06:06 -04:00
if not dom_uuid :
2018-09-20 03:25:58 -04:00
common . stopZKConnection ( zk_conn )
return False , ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain )
2018-10-27 15:27:08 -04:00
current_node = zkhandler . readdata ( zk_conn , ' /domains/ {} /node ' . format ( dom_uuid ) )
2018-09-20 03:25:58 -04:00
2018-10-14 02:01:35 -04:00
if target_node == None :
target_node = common . findTargetNode ( zk_conn , selector , dom_uuid )
2018-09-20 03:25:58 -04:00
else :
2018-10-14 02:01:35 -04:00
if target_node == current_node :
2018-09-20 03:25:58 -04:00
common . stopZKConnection ( zk_conn )
2018-10-14 02:01:35 -04:00
return False , ' ERROR: VM " {} " is already running on node " {} " . ' . format ( dom_uuid , current_node )
2018-09-20 03:25:58 -04:00
# Verify node is valid
2018-10-14 02:01:35 -04:00
common . verifyNode ( zk_conn , target_node )
2018-09-20 03:25:58 -04:00
2018-10-27 15:27:08 -04:00
current_vm_state = zkhandler . readdata ( zk_conn , ' /domains/ {} /state ' . format ( dom_uuid ) )
2018-09-20 03:25:58 -04:00
if current_vm_state == ' start ' :
2018-10-27 15:27:08 -04:00
zkhandler . writedata ( zk_conn , {
' /domains/ {} /state ' . format ( dom_uuid ) : ' migrate ' ,
' /domains/ {} /node ' . format ( dom_uuid ) : target_node ,
' /domains/ {} /lastnode ' . format ( dom_uuid ) : ' '
} )
2018-09-20 03:25:58 -04:00
else :
2018-10-27 15:27:08 -04:00
zkhandler . writedata ( zk_conn , {
' /domains/ {} /node ' . format ( dom_uuid ) : target_node ,
' /domains/ {} /lastnode ' . format ( dom_uuid ) : ' '
} )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
return True , ' Permanently migrating VM " {} " to node " {} " . ' . format ( dom_uuid , target_node )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
def migrate_vm ( zk_conn , domain , target_node , selector , force_migrate , is_cli = False ) :
2018-09-20 03:25:58 -04:00
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID ( zk_conn , domain )
2019-04-11 19:06:06 -04:00
if not dom_uuid :
2018-09-20 03:25:58 -04:00
common . stopZKConnection ( zk_conn )
return False , ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain )
# Get state and verify we're OK to proceed
2018-10-27 15:27:08 -04:00
current_state = zkhandler . readdata ( zk_conn , ' /domains/ {} /state ' . format ( dom_uuid ) )
2018-09-20 03:25:58 -04:00
if current_state != ' start ' :
target_state = ' start '
else :
target_state = ' migrate '
2018-10-27 15:27:08 -04:00
current_node = zkhandler . readdata ( zk_conn , ' /domains/ {} /node ' . format ( dom_uuid ) )
last_node = zkhandler . readdata ( zk_conn , ' /domains/ {} /lastnode ' . format ( dom_uuid ) )
2018-09-20 03:25:58 -04:00
2018-10-14 02:01:35 -04:00
if last_node != ' ' and force_migrate != True :
2019-05-20 22:15:28 -04:00
if is_cli :
click . echo ( ' ERROR: VM " {} " has been previously migrated. ' . format ( dom_uuid ) )
click . echo ( ' > Last node: {} ' . format ( last_node ) )
click . echo ( ' > Current node: {} ' . format ( current_node ) )
click . echo ( ' Run `vm unmigrate` to restore the VM to its previous node, or use `--force` to override this check. ' )
return False , ' '
else :
return False , ' ERROR: VM " {} " has been previously migrated. ' . format ( dom_uuid )
2018-09-20 03:25:58 -04:00
2018-10-14 02:01:35 -04:00
if target_node == None :
2018-10-21 22:10:05 -04:00
target_node = common . findTargetNode ( zk_conn , selector , dom_uuid )
2018-09-20 03:25:58 -04:00
else :
2018-10-14 02:01:35 -04:00
if target_node == current_node :
2018-09-20 03:25:58 -04:00
common . stopZKConnection ( zk_conn )
2018-10-14 02:01:35 -04:00
return False , ' ERROR: VM " {} " is already running on node " {} " . ' . format ( dom_uuid , current_node )
2018-09-20 03:25:58 -04:00
# Verify node is valid
2018-10-14 02:01:35 -04:00
common . verifyNode ( zk_conn , target_node )
2018-09-20 03:25:58 -04:00
2018-10-27 15:27:08 -04:00
zkhandler . writedata ( zk_conn , {
' /domains/ {} /state ' . format ( dom_uuid ) : ' migrate ' ,
' /domains/ {} /node ' . format ( dom_uuid ) : target_node ,
' /domains/ {} /lastnode ' . format ( dom_uuid ) : current_node
} )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
return True , ' Migrating VM " {} " to node " {} " . ' . format ( dom_uuid , target_node )
2018-09-20 03:25:58 -04:00
def unmigrate_vm ( zk_conn , domain ) :
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID ( zk_conn , domain )
2019-04-11 19:06:06 -04:00
if not dom_uuid :
2018-09-20 03:25:58 -04:00
common . stopZKConnection ( zk_conn )
return False , ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain )
# Get state and verify we're OK to proceed
2018-10-27 15:27:08 -04:00
current_state = zkhandler . readdata ( zk_conn , ' /domains/ {} /state ' . format ( dom_uuid ) )
2018-09-20 03:25:58 -04:00
if current_state != ' start ' :
2019-03-20 10:19:01 -04:00
# If the current state isn't start, preserve it; we're not doing live migration
target_state = current_state
2018-09-20 03:25:58 -04:00
else :
target_state = ' migrate '
2018-10-27 15:27:08 -04:00
target_node = zkhandler . readdata ( zk_conn , ' /domains/ {} /lastnode ' . format ( dom_uuid ) )
2018-09-20 03:25:58 -04:00
2018-10-14 02:01:35 -04:00
if target_node == ' ' :
2018-09-20 03:25:58 -04:00
common . stopZKConnection ( zk_conn )
return False , ' ERROR: VM " {} " has not been previously migrated. ' . format ( dom_uuid )
2018-10-27 15:27:08 -04:00
zkhandler . writedata ( zk_conn , {
' /domains/ {} /state ' . format ( dom_uuid ) : target_state ,
' /domains/ {} /node ' . format ( dom_uuid ) : target_node ,
' /domains/ {} /lastnode ' . format ( dom_uuid ) : ' '
} )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
return True , ' Unmigrating VM " {} " back to node " {} " . ' . format ( dom_uuid , target_node )
2018-09-20 03:25:58 -04:00
2019-04-11 19:06:06 -04:00
def get_console_log ( zk_conn , domain , lines = 1000 ) :
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID ( zk_conn , domain )
if not dom_uuid :
return False , ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain )
# Get the data from ZK
console_log = zkhandler . readdata ( zk_conn , ' /domains/ {} /consolelog ' . format ( dom_uuid ) )
# Shrink the log buffer to length lines
shrunk_log = console_log . split ( ' \n ' ) [ - lines : ]
loglines = ' \n ' . join ( shrunk_log )
# Show it in the pager (less)
try :
pager = subprocess . Popen ( [ ' less ' , ' -R ' ] , stdin = subprocess . PIPE )
pager . communicate ( input = loglines . encode ( ' utf8 ' ) )
except FileNotFoundError :
return False , ' ERROR: The " less " pager is required to view console logs. '
return True , ' '
def follow_console_log ( zk_conn , domain , lines = 10 ) :
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID ( zk_conn , domain )
if not dom_uuid :
return False , ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain )
# Get the initial data from ZK
console_log = zkhandler . readdata ( zk_conn , ' /domains/ {} /consolelog ' . format ( dom_uuid ) )
# Shrink the log buffer to length lines
shrunk_log = console_log . split ( ' \n ' ) [ - lines : ]
loglines = ' \n ' . join ( shrunk_log )
# Print the initial data and begin following
print ( loglines , end = ' ' )
while True :
# Grab the next line set
new_console_log = zkhandler . readdata ( zk_conn , ' /domains/ {} /consolelog ' . format ( dom_uuid ) )
# 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 )
return True , ' '
2019-05-20 22:15:28 -04:00
def get_info ( zk_conn , domain ) :
# Validate and obtain alternate passed value
dom_uuid = getDomainUUID ( zk_conn , domain )
if not dom_uuid :
common . stopZKConnection ( zk_conn )
return False , ' ERROR: No VM named " {} " is present in the cluster. ' . format ( domain )
# Gather information from XML config and print it
domain_information = getInformationFromXML ( zk_conn , dom_uuid )
if domain_information == None :
return False , ' ERROR: Could not get information about VM " {} " . ' . format ( domain )
return True , domain_information
def get_list ( zk_conn , node , state , limit ) :
2018-10-14 02:01:35 -04:00
if node != None :
2018-09-20 03:25:58 -04:00
# Verify node is valid
2018-10-14 02:01:35 -04:00
common . verifyNode ( zk_conn , node )
2018-09-20 03:25:58 -04:00
2019-03-20 11:31:54 -04:00
if state != None :
valid_states = [ ' start ' , ' restart ' , ' shutdown ' , ' stop ' , ' failed ' , ' migrate ' , ' unmigrate ' ]
if not state in valid_states :
return False , ' VM state " {} " is not valid. ' . format ( state )
2018-10-27 15:27:08 -04:00
full_vm_list = zkhandler . listchildren ( zk_conn , ' /domains ' )
2018-09-20 03:25:58 -04:00
vm_list = [ ]
2019-03-12 23:52:59 -04:00
# Set our limit to a sensible regex
if limit != None :
try :
# Implcitly assume fuzzy limits
if re . match ( ' \ ^.* ' , limit ) == None :
limit = ' .* ' + limit
if re . match ( ' .* \ $ ' , limit ) == None :
limit = limit + ' .* '
except Exception as e :
return False , ' Regex Error: {} ' . format ( e )
2018-09-20 03:25:58 -04:00
# If we're limited, remove other nodes' VMs
2019-05-20 22:15:28 -04:00
vm_node = { }
vm_state = { }
2018-09-25 02:20:32 -04:00
for vm in full_vm_list :
2018-09-20 03:25:58 -04:00
# Check we don't match the limit
2018-09-25 02:20:32 -04:00
name = zkhandler . readdata ( zk_conn , ' /domains/ {} ' . format ( vm ) )
2019-03-12 21:39:17 -04:00
vm_node [ vm ] = zkhandler . readdata ( zk_conn , ' /domains/ {} /node ' . format ( vm ) )
2019-03-20 11:31:54 -04:00
vm_state [ vm ] = zkhandler . readdata ( zk_conn , ' /domains/ {} /state ' . format ( vm ) )
2019-03-12 23:52:59 -04:00
# Handle limiting
2018-09-20 03:25:58 -04:00
if limit != None :
try :
2018-09-25 02:20:32 -04:00
if re . match ( limit , vm ) != None :
2019-03-20 11:31:54 -04:00
if node == None and state == None :
2019-05-20 22:15:28 -04:00
vm_list . append ( getInformationFromXML ( zk_conn , vm ) )
2018-09-25 02:20:32 -04:00
else :
2019-03-20 11:31:54 -04:00
if vm_node [ vm ] == node or vm_state [ vm ] == state :
2019-05-20 22:15:28 -04:00
vm_list . append ( getInformationFromXML ( zk_conn , vm ) )
2018-09-25 02:20:32 -04:00
if re . match ( limit , name ) != None :
2019-03-20 11:31:54 -04:00
if node == None and state == None :
2019-05-20 22:15:28 -04:00
vm_list . append ( getInformationFromXML ( zk_conn , vm ) )
2018-09-25 02:20:32 -04:00
else :
2019-03-20 11:31:54 -04:00
if vm_node [ vm ] == node or vm_state [ vm ] == state :
2019-05-20 22:15:28 -04:00
vm_list . append ( getInformationFromXML ( zk_conn , vm ) )
2018-09-20 03:25:58 -04:00
except Exception as e :
2018-09-25 02:20:32 -04:00
return False , ' Regex Error: {} ' . format ( e )
2018-09-20 03:25:58 -04:00
else :
2018-10-14 02:01:35 -04:00
# Check node to avoid unneeded ZK calls
2019-03-20 11:31:54 -04:00
if node == None and state == None :
2019-05-20 22:15:28 -04:00
vm_list . append ( getInformationFromXML ( zk_conn , vm ) )
2018-09-25 02:20:32 -04:00
else :
2019-03-20 11:31:54 -04:00
if vm_node [ vm ] == node or vm_state [ vm ] == state :
2019-05-20 22:15:28 -04:00
vm_list . append ( getInformationFromXML ( zk_conn , vm ) )
return True , vm_list
#
# CLI-specific functions
#
def format_info ( zk_conn , domain_information , long_output ) :
# 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 ( ' {} 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 ' ] ) )
if long_output == True :
# 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 ' ] ) ) )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
# PVC cluster information
ainformation . append ( ' ' )
dstate_colour = {
' start ' : ansiprint . green ( ) ,
' restart ' : ansiprint . yellow ( ) ,
' shutdown ' : ansiprint . yellow ( ) ,
' stop ' : ansiprint . red ( ) ,
' failed ' : ansiprint . red ( ) ,
' migrate ' : ansiprint . blue ( ) ,
' unmigrate ' : ansiprint . blue ( )
}
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 ' ] ) )
ainformation . append ( ' {} Previous Node: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' last_node ' ] ) )
# Get a failure reason if applicable
if domain_information [ ' failed_reason ' ] != ' ' :
click . echo ( ' ' )
click . echo ( ' {} Failure reason: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , domain_information [ ' failed_reason ' ] ) )
# 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 ]
2018-09-20 03:25:58 -04:00
else :
2019-05-20 22:15:28 -04:00
net_vni = re . sub ( ' br ' , ' ' , net [ ' source ' ] )
net_exists = zkhandler . exists ( zk_conn , ' /networks/ {} ' . format ( net_vni ) )
if not net_exists and net_vni != ' cluster ' :
net_list . append ( ansiprint . red ( ) + net_vni + ansiprint . end ( ) + ' [invalid] ' )
else :
net_list . append ( net_vni )
ainformation . append ( ' ' )
ainformation . append ( ' {} Networks: {} {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , ' , ' . join ( net_list ) ) )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
if long_output == True :
# 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
ainformation . append ( ' {0} Disks: {1} {2} ID Type { 3: < {width} } Dev Bus {4} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , ansiprint . bold ( ) , ' Name ' , ansiprint . end ( ) , width = name_length ) )
for disk in domain_information [ ' disks ' ] :
ainformation . append ( ' {0: <3} {1: <5} { 2: < {width} } {3: <4} {4: <5} ' . format ( domain_information [ ' disks ' ] . index ( disk ) , disk [ ' type ' ] , disk [ ' name ' ] , disk [ ' dev ' ] , disk [ ' bus ' ] , width = name_length ) )
ainformation . append ( ' ' )
ainformation . append ( ' {} Interfaces: {} {} ID Type Source Model MAC {} ' . format ( ansiprint . purple ( ) , ansiprint . end ( ) , ansiprint . bold ( ) , ansiprint . end ( ) ) )
for net in domain_information [ ' nets ' ] :
ainformation . append ( ' {0: <3} {1: <8} {2: <10} {3: <8} {4} ' . format ( domain_information [ ' nets ' ] . index ( net ) , net [ ' type ' ] , net [ ' source ' ] , net [ ' model ' ] , net [ ' mac ' ] ) )
# 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 ' ] ) )
2018-09-20 03:25:58 -04:00
2019-05-20 22:15:28 -04:00
# Join it all together
information = ' \n ' . join ( ainformation )
click . echo ( information )
click . echo ( ' ' )
def format_list ( zk_conn , vm_list , raw ) :
# Handle raw mode since it just lists the names
2019-03-12 21:40:52 -04:00
if raw :
2019-03-12 21:46:09 -04:00
for vm in sorted ( vm_name . values ( ) ) :
click . echo ( vm )
2019-03-12 21:40:52 -04:00
return True , ' '
2019-05-20 22:15:28 -04:00
vm_list_output = [ ]
2018-09-20 03:25:58 -04:00
# Determine optimal column widths
2018-10-14 02:01:35 -04:00
# Dynamic columns: node_name, node, migrated
2018-11-01 23:24:38 -04:00
vm_name_length = 5
2018-11-02 00:42:44 -04:00
vm_uuid_length = 37
vm_state_length = 6
2018-10-14 02:01:35 -04:00
vm_nets_length = 9
2018-11-02 00:42:44 -04:00
vm_ram_length = 8
vm_vcpu_length = 6
vm_node_length = 8
2018-10-14 02:01:35 -04:00
vm_migrated_length = 10
2019-05-20 22:15:28 -04:00
for domain_information in vm_list :
# 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 )
2018-09-20 03:25:58 -04:00
# vm_name column
2019-05-20 22:15:28 -04:00
_vm_name_length = len ( domain_information [ ' name ' ] ) + 1
2018-09-20 03:25:58 -04:00
if _vm_name_length > vm_name_length :
vm_name_length = _vm_name_length
2018-11-02 00:42:44 -04:00
# vm_state column
2019-05-20 22:15:28 -04:00
_vm_state_length = len ( domain_information [ ' state ' ] ) + 1
2018-11-02 00:42:44 -04:00
if _vm_state_length > vm_state_length :
vm_state_length = _vm_state_length
2018-10-14 02:01:35 -04:00
# vm_nets column
2019-05-20 22:15:28 -04:00
_vm_nets_length = len ( ' , ' . join ( net_list ) ) + 1
2018-10-14 02:01:35 -04:00
if _vm_nets_length > vm_nets_length :
vm_nets_length = _vm_nets_length
2018-11-02 00:42:44 -04:00
# vm_node column
2019-05-20 22:15:28 -04:00
_vm_node_length = len ( domain_information [ ' node ' ] ) + 1
2018-11-02 00:42:44 -04:00
if _vm_node_length > vm_node_length :
vm_node_length = _vm_node_length
2018-09-20 03:25:58 -04:00
# vm_migrated column
2019-05-20 22:15:28 -04:00
_vm_migrated_length = len ( domain_information [ ' migrated ' ] ) + 1
2018-09-20 03:25:58 -04:00
if _vm_migrated_length > vm_migrated_length :
vm_migrated_length = _vm_migrated_length
# Format the string (header)
vm_list_output . append (
2018-11-02 00:42:44 -04:00
' {bold} { vm_name: < {vm_name_length} } { vm_uuid: < {vm_uuid_length} } \
{ vm_state_colour } { vm_state : < { vm_state_length } } { end_colour } \
2018-10-14 02:01:35 -04:00
{ vm_networks : < { vm_nets_length } } \
2018-11-02 00:42:44 -04:00
{ vm_memory : < { vm_ram_length } } { vm_vcpu : < { vm_vcpu_length } } \
2018-10-14 02:01:35 -04:00
{ vm_node : < { vm_node_length } } \
2018-09-20 03:25:58 -04:00
{ vm_migrated : < { vm_migrated_length } } { end_bold } ' .format(
vm_name_length = vm_name_length ,
2018-11-02 00:42:44 -04:00
vm_uuid_length = vm_uuid_length ,
vm_state_length = vm_state_length ,
2018-10-14 02:01:35 -04:00
vm_nets_length = vm_nets_length ,
2018-11-02 00:42:44 -04:00
vm_ram_length = vm_ram_length ,
vm_vcpu_length = vm_vcpu_length ,
vm_node_length = vm_node_length ,
2018-09-20 03:25:58 -04:00
vm_migrated_length = vm_migrated_length ,
2018-10-20 15:28:25 -04:00
bold = ansiprint . bold ( ) ,
end_bold = ansiprint . end ( ) ,
2018-09-20 03:25:58 -04:00
vm_state_colour = ' ' ,
end_colour = ' ' ,
vm_name = ' Name ' ,
vm_uuid = ' UUID ' ,
vm_state = ' State ' ,
2018-10-14 02:01:35 -04:00
vm_networks = ' Networks ' ,
2018-11-01 23:24:38 -04:00
vm_memory = ' RAM (M) ' ,
2018-09-20 03:25:58 -04:00
vm_vcpu = ' vCPUs ' ,
2018-10-14 02:01:35 -04:00
vm_node = ' Node ' ,
2018-09-20 03:25:58 -04:00
vm_migrated = ' Migrated '
)
)
# Format the string (elements)
2019-05-20 22:15:28 -04:00
for domain_information in vm_list :
if domain_information [ ' state ' ] == ' start ' :
2018-10-20 15:28:25 -04:00
vm_state_colour = ansiprint . green ( )
2019-05-20 22:15:28 -04:00
elif domain_information [ ' state ' ] == ' restart ' :
2018-10-20 15:28:25 -04:00
vm_state_colour = ansiprint . yellow ( )
2019-05-20 22:15:28 -04:00
elif domain_information [ ' state ' ] == ' shutdown ' :
2018-10-20 15:28:25 -04:00
vm_state_colour = ansiprint . yellow ( )
2019-05-20 22:15:28 -04:00
elif domain_information [ ' state ' ] == ' stop ' :
2018-10-20 15:28:25 -04:00
vm_state_colour = ansiprint . red ( )
2019-05-20 22:15:28 -04:00
elif domain_information [ ' state ' ] == ' failed ' :
2018-10-20 15:28:25 -04:00
vm_state_colour = ansiprint . red ( )
2018-09-20 03:25:58 -04:00
else :
2018-10-20 15:28:25 -04:00
vm_state_colour = ansiprint . blue ( )
2018-09-20 03:25:58 -04:00
2018-10-20 15:27:07 -04:00
# Handle colouring for an invalid network config
net_list = [ ]
2019-05-20 22:15:28 -04:00
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_exists = zkhandler . exists ( zk_conn , ' /networks/ {} ' . format ( net_vni ) )
if not net_exists and net_vni != ' cluster ' :
net_list . append ( ansiprint . red ( ) + net_vni + ansiprint . end ( ) )
else :
net_list . append ( net_vni )
2018-10-20 15:27:07 -04:00
2018-09-20 03:25:58 -04:00
vm_list_output . append (
2018-11-02 00:42:44 -04:00
' {bold} { vm_name: < {vm_name_length} } { vm_uuid: < {vm_uuid_length} } \
{ vm_state_colour } { vm_state : < { vm_state_length } } { end_colour } \
2019-05-20 22:15:28 -04:00
{ vm_networks : < { vm_nets_length } } \
2018-11-02 00:42:44 -04:00
{ vm_memory : < { vm_ram_length } } { vm_vcpu : < { vm_vcpu_length } } \
2018-10-14 02:01:35 -04:00
{ vm_node : < { vm_node_length } } \
2018-09-20 03:25:58 -04:00
{ vm_migrated : < { vm_migrated_length } } { end_bold } ' .format(
vm_name_length = vm_name_length ,
2018-11-02 00:42:44 -04:00
vm_uuid_length = vm_uuid_length ,
vm_state_length = vm_state_length ,
2018-10-14 02:01:35 -04:00
vm_nets_length = vm_nets_length ,
2018-11-02 00:42:44 -04:00
vm_ram_length = vm_ram_length ,
vm_vcpu_length = vm_vcpu_length ,
vm_node_length = vm_node_length ,
2018-09-20 03:25:58 -04:00
vm_migrated_length = vm_migrated_length ,
bold = ' ' ,
end_bold = ' ' ,
vm_state_colour = vm_state_colour ,
2018-10-20 15:28:25 -04:00
end_colour = ansiprint . end ( ) ,
2019-05-20 22:15:28 -04:00
vm_name = domain_information [ ' name ' ] ,
vm_uuid = domain_information [ ' uuid ' ] ,
vm_state = domain_information [ ' state ' ] ,
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 ' ]
2018-09-20 03:25:58 -04:00
)
)
click . echo ( ' \n ' . join ( sorted ( vm_list_output ) ) )
return True , ' '
2019-05-20 22:15:28 -04:00