2018-05-31 20:26:44 -04:00
#!/usr/bin/env python3
2018-06-06 01:47:53 -04:00
# pvcd.py - PVC client command-line interface
# 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/>.
#
###############################################################################
2018-06-14 12:07:46 -04:00
import os
import socket
import time
2018-06-14 22:39:24 -04:00
import uuid
2018-07-19 21:58:11 -04:00
import re
2018-07-20 00:38:31 -04:00
import tempfile
import subprocess
import difflib
import colorama
2018-06-14 12:07:46 -04:00
import click
import lxml . objectify
import configparser
import kazoo . client
2018-06-14 11:57:36 -04:00
###############################################################################
# Supplemental functions
###############################################################################
2018-08-11 14:05:16 -04:00
#
# ansiiprint output
#
class ansiiprint :
# ANSII colours for output
def red ( ) :
return ' \033 [91m '
def blue ( ) :
return ' \033 [94m '
def green ( ) :
return ' \033 [92m '
def yellow ( ) :
return ' \033 [93m '
def purple ( ) :
return ' \033 [95m '
def bold ( ) :
return ' \033 [1m '
def end ( ) :
return ' \033 [0m '
2018-06-14 11:57:36 -04:00
#
# Validate a UUID
#
def validateUUID ( dom_uuid ) :
try :
uuid . UUID ( dom_uuid )
return True
except :
return False
#
# Connect and disconnect from Zookeeper
#
def startZKConnection ( zk_host ) :
2018-06-17 21:55:39 -04:00
zk_conn = kazoo . client . KazooClient ( hosts = zk_host )
zk_conn . start ( )
return zk_conn
2018-06-14 11:57:36 -04:00
2018-06-17 21:55:39 -04:00
def stopZKConnection ( zk_conn ) :
zk_conn . stop ( )
zk_conn . close ( )
2018-06-14 11:57:36 -04:00
return 0
#
# XML information parsing functions
#
# Get the main details for a VM object from XML
def getDomainMainDetails ( parsed_xml ) :
# Get the information we want from it
duuid = str ( parsed_xml . uuid )
dname = str ( parsed_xml . name )
dmemory = str ( parsed_xml . memory )
dmemory_unit = str ( parsed_xml . memory . attrib [ ' unit ' ] )
if dmemory_unit == ' KiB ' :
dmemory = str ( int ( dmemory ) * 1024 )
elif dmemory_unit == ' GiB ' :
dmemory = str ( int ( dmemory ) / 1024 )
dvcpu = str ( parsed_xml . vcpu )
try :
dvcputopo = ' {} / {} / {} ' . format ( parsed_xml . cpu . topology . attrib [ ' sockets ' ] , parsed_xml . cpu . topology . attrib [ ' cores ' ] , parsed_xml . cpu . topology . attrib [ ' threads ' ] )
except :
dvcputopo = ' N/A '
return duuid , dname , dmemory , dvcpu , dvcputopo
# Get long-format details
def getDomainExtraDetails ( parsed_xml ) :
dtype = parsed_xml . os . type
darch = parsed_xml . os . type . attrib [ ' arch ' ]
dmachine = parsed_xml . os . type . attrib [ ' machine ' ]
dconsole = parsed_xml . devices . console . attrib [ ' type ' ]
demulator = parsed_xml . devices . emulator
return dtype , darch , dmachine , dconsole , demulator
# Get CPU features
def getDomainCPUFeatures ( parsed_xml ) :
dfeatures = [ ]
for feature in parsed_xml . features . getchildren ( ) :
dfeatures . append ( feature . tag )
return dfeatures
# Get disk devices
def getDomainDisks ( parsed_xml ) :
ddisks = [ ]
for device in parsed_xml . devices . getchildren ( ) :
if device . tag == ' disk ' :
disk_attrib = device . source . attrib
disk_target = device . target . attrib
disk_type = device . attrib [ ' type ' ]
if disk_type == ' network ' :
disk_obj = { ' type ' : disk_attrib . get ( ' protocol ' ) , ' name ' : disk_attrib . get ( ' name ' ) , ' dev ' : disk_target . get ( ' dev ' ) , ' bus ' : disk_target . get ( ' bus ' ) }
elif disk_type == ' file ' :
disk_obj = { ' type ' : ' file ' , ' name ' : disk_attrib . get ( ' file ' ) , ' dev ' : disk_target . get ( ' dev ' ) , ' bus ' : disk_target . get ( ' bus ' ) }
else :
disk_obj = { }
ddisks . append ( disk_obj )
return ddisks
# Get network devices
def getDomainNetworks ( parsed_xml ) :
dnets = [ ]
for device in parsed_xml . devices . getchildren ( ) :
if device . tag == ' interface ' :
net_type = device . attrib [ ' type ' ]
net_mac = device . mac . attrib [ ' address ' ]
net_bridge = device . source . attrib [ net_type ]
net_model = device . model . attrib [ ' type ' ]
net_obj = { ' type ' : net_type , ' mac ' : net_mac , ' source ' : net_bridge , ' model ' : net_model }
dnets . append ( net_obj )
return dnets
# Get controller devices
def getDomainControllers ( parsed_xml ) :
dcontrollers = [ ]
for device in parsed_xml . devices . getchildren ( ) :
if device . tag == ' controller ' :
controller_type = device . attrib [ ' type ' ]
try :
controller_model = device . attrib [ ' model ' ]
except KeyError :
controller_model = ' none '
controller_obj = { ' type ' : controller_type , ' model ' : controller_model }
dcontrollers . append ( controller_obj )
return dcontrollers
# Parse an XML object
2018-06-17 21:55:39 -04:00
def getDomainXML ( zk_conn , dom_uuid ) :
2018-06-14 11:57:36 -04:00
try :
2018-06-17 21:55:39 -04:00
xml = zk_conn . get ( ' /domains/ %s /xml ' % dom_uuid ) [ 0 ] . decode ( ' ascii ' )
2018-06-14 11:57:36 -04:00
except :
return None
# Parse XML using lxml.objectify
parsed_xml = lxml . objectify . fromstring ( xml )
return parsed_xml
# Root functions
2018-06-17 21:55:39 -04:00
def getInformationFromNode ( zk_conn , node_name , long_output ) :
node_daemon_state = zk_conn . get ( ' /nodes/ {} /daemonstate ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' )
node_domain_state = zk_conn . get ( ' /nodes/ {} /domainstate ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' )
node_cpu_count = zk_conn . get ( ' /nodes/ {} /staticdata ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' ) . split ( ) [ 0 ]
node_kernel = zk_conn . get ( ' /nodes/ {} /staticdata ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' ) . split ( ) [ 1 ]
node_os = zk_conn . get ( ' /nodes/ {} /staticdata ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' ) . split ( ) [ 2 ]
node_arch = zk_conn . get ( ' /nodes/ {} /staticdata ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' ) . split ( ) [ 3 ]
node_mem_used = zk_conn . get ( ' /nodes/ {} /memused ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' )
node_mem_free = zk_conn . get ( ' /nodes/ {} /memfree ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-14 11:57:36 -04:00
node_mem_total = int ( node_mem_used ) + int ( node_mem_free )
2018-07-16 12:26:28 -04:00
node_load = zk_conn . get ( ' /nodes/ {} /cpuload ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-17 21:55:39 -04:00
node_domains_count = zk_conn . get ( ' /nodes/ {} /domainscount ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' )
node_running_domains = zk_conn . get ( ' /nodes/ {} /runningdomains ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' ) . split ( )
2018-06-14 11:57:36 -04:00
node_mem_allocated = 0
for domain in node_running_domains :
2018-08-20 12:03:53 -04:00
try :
parsed_xml = getDomainXML ( zk_conn , domain )
duuid , dname , dmemory , dvcpu , dvcputopo = getDomainMainDetails ( parsed_xml )
2018-08-20 13:43:00 -04:00
node_mem_allocated + = int ( dmemory )
2018-08-20 12:03:53 -04:00
except AttributeError :
click . echo ( ' Error: Domain {} does not exist. ' . format ( domain ) )
2018-06-14 11:57:36 -04:00
if node_daemon_state == ' run ' :
daemon_state_colour = ansiiprint . green ( )
elif node_daemon_state == ' stop ' :
daemon_state_colour = ansiiprint . red ( )
elif node_daemon_state == ' init ' :
daemon_state_colour = ansiiprint . yellow ( )
elif node_daemon_state == ' dead ' :
daemon_state_colour = ansiiprint . red ( ) + ansiiprint . bold ( )
else :
daemon_state_colour = ansiiprint . blue ( )
if node_domain_state == ' ready ' :
domain_state_colour = ansiiprint . green ( )
else :
domain_state_colour = ansiiprint . blue ( )
# Format a nice output; do this line-by-line then concat the elements at the end
ainformation = [ ]
ainformation . append ( ' {} Hypervisor Node information: {} ' . format ( ansiiprint . bold ( ) , ansiiprint . end ( ) ) )
ainformation . append ( ' ' )
# Basic information
ainformation . append ( ' {} Name: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , node_name ) )
ainformation . append ( ' {} Daemon State: {} {} {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , daemon_state_colour , node_daemon_state , ansiiprint . end ( ) ) )
ainformation . append ( ' {} Domain State: {} {} {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , domain_state_colour , node_domain_state , ansiiprint . end ( ) ) )
2018-06-15 01:57:01 -04:00
ainformation . append ( ' {} Active VM Count: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , node_domains_count ) )
2018-06-14 11:57:36 -04:00
if long_output == True :
ainformation . append ( ' ' )
ainformation . append ( ' {} Architecture: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , node_arch ) )
ainformation . append ( ' {} Operating System: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , node_os ) )
ainformation . append ( ' {} Kernel Version: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , node_kernel ) )
ainformation . append ( ' ' )
ainformation . append ( ' {} CPUs: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , node_cpu_count ) )
2018-07-16 12:26:28 -04:00
ainformation . append ( ' {} Load: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , node_load ) )
2018-06-14 11:57:36 -04:00
ainformation . append ( ' {} Total RAM (MiB): {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , node_mem_total ) )
ainformation . append ( ' {} Used RAM (MiB): {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , node_mem_used ) )
ainformation . append ( ' {} Free RAM (MiB): {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , node_mem_free ) )
ainformation . append ( ' {} Allocated RAM (MiB): {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , node_mem_allocated ) )
# Join it all together
information = ' \n ' . join ( ainformation )
return information
2018-06-17 21:55:39 -04:00
def getInformationFromXML ( zk_conn , uuid , long_output ) :
2018-06-14 11:57:36 -04:00
# Obtain the contents of the XML from Zookeeper
try :
2018-06-17 21:55:39 -04:00
dstate = zk_conn . get ( ' /domains/ {} /state ' . format ( uuid ) ) [ 0 ] . decode ( ' ascii ' )
dhypervisor = zk_conn . get ( ' /domains/ {} /hypervisor ' . format ( uuid ) ) [ 0 ] . decode ( ' ascii ' )
dlasthypervisor = zk_conn . get ( ' /domains/ {} /lasthypervisor ' . format ( uuid ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-14 11:57:36 -04:00
except :
return None
if dlasthypervisor == ' ' :
dlasthypervisor = ' N/A '
2018-08-20 12:03:53 -04:00
try :
parsed_xml = getDomainXML ( zk_conn , uuid )
duuid , dname , dmemory , dvcpu , dvcputopo = getDomainMainDetails ( parsed_xml )
except AttributeError :
click . echo ( ' Error: Domain {} does not exist. ' . format ( domain ) )
2018-06-14 11:57:36 -04:00
if long_output == True :
dtype , darch , dmachine , dconsole , demulator = getDomainExtraDetails ( parsed_xml )
dfeatures = getDomainCPUFeatures ( parsed_xml )
ddisks = getDomainDisks ( parsed_xml )
dnets = getDomainNetworks ( parsed_xml )
dcontrollers = getDomainControllers ( parsed_xml )
# Format a nice output; do this line-by-line then concat the elements at the end
ainformation = [ ]
ainformation . append ( ' {} Virtual machine information: {} ' . format ( ansiiprint . bold ( ) , ansiiprint . end ( ) ) )
ainformation . append ( ' ' )
# Basic information
ainformation . append ( ' {} UUID: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , duuid ) )
ainformation . append ( ' {} Name: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , dname ) )
ainformation . append ( ' {} Memory (MiB): {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , dmemory ) )
ainformation . append ( ' {} vCPUs: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , dvcpu ) )
ainformation . append ( ' {} Topology (S/C/T): {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , dvcputopo ) )
if long_output == True :
# Virtualization information
ainformation . append ( ' ' )
ainformation . append ( ' {} Emulator: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , demulator ) )
ainformation . append ( ' {} Type: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , dtype ) )
ainformation . append ( ' {} Arch: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , darch ) )
ainformation . append ( ' {} Machine: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , dmachine ) )
ainformation . append ( ' {} Features: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , ' ' . join ( dfeatures ) ) )
# PVC cluster information
ainformation . append ( ' ' )
dstate_colour = {
' start ' : ansiiprint . green ( ) ,
' restart ' : ansiiprint . yellow ( ) ,
' shutdown ' : ansiiprint . yellow ( ) ,
' stop ' : ansiiprint . red ( ) ,
2018-06-14 12:16:04 -04:00
' failed ' : ansiiprint . red ( ) ,
2018-06-14 11:57:36 -04:00
' migrate ' : ansiiprint . blue ( ) ,
' unmigrate ' : ansiiprint . blue ( )
}
ainformation . append ( ' {} State: {} {} {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , dstate_colour [ dstate ] , dstate , ansiiprint . end ( ) ) )
ainformation . append ( ' {} Active Hypervisor: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , dhypervisor ) )
ainformation . append ( ' {} Last Hypervisor: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , dlasthypervisor ) )
if long_output == True :
# Disk list
ainformation . append ( ' ' )
name_length = 0
for disk in ddisks :
_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 ( ansiiprint . purple ( ) , ansiiprint . end ( ) , ansiiprint . bold ( ) , ' Name ' , ansiiprint . end ( ) , width = name_length ) )
for disk in ddisks :
ainformation . append ( ' {0: <3} {1: <5} { 2: < {width} } {3: <4} {4: <5} ' . format ( ddisks . index ( disk ) , disk [ ' type ' ] , disk [ ' name ' ] , disk [ ' dev ' ] , disk [ ' bus ' ] , width = name_length ) )
# Network list
ainformation . append ( ' ' )
ainformation . append ( ' {} Interfaces: {} {} ID Type Source Model MAC {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , ansiiprint . bold ( ) , ansiiprint . end ( ) ) )
for net in dnets :
ainformation . append ( ' {0: <3} {1: <8} {2: <10} {3: <8} {4} ' . format ( dnets . index ( net ) , net [ ' type ' ] , net [ ' source ' ] , net [ ' model ' ] , net [ ' mac ' ] ) )
# Controller list
ainformation . append ( ' ' )
ainformation . append ( ' {} Controllers: {} {} ID Type Model {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , ansiiprint . bold ( ) , ansiiprint . end ( ) ) )
for controller in dcontrollers :
ainformation . append ( ' {0: <3} {1: <14} {2: <8} ' . format ( dcontrollers . index ( controller ) , controller [ ' type ' ] , controller [ ' model ' ] ) )
# Join it all together
information = ' \n ' . join ( ainformation )
return information
#
# Cluster search functions
#
2018-06-17 21:55:39 -04:00
def getClusterDomainList ( zk_conn ) :
2018-06-14 11:57:36 -04:00
# Get a list of UUIDs by listing the children of /domains
2018-06-17 21:55:39 -04:00
uuid_list = zk_conn . get_children ( ' /domains ' )
2018-06-14 11:57:36 -04:00
name_list = [ ]
# For each UUID, get the corresponding name from the data
for uuid in uuid_list :
2018-06-17 21:55:39 -04:00
name_list . append ( zk_conn . get ( ' /domains/ %s ' % uuid ) [ 0 ] . decode ( ' ascii ' ) )
2018-06-14 11:57:36 -04:00
return uuid_list , name_list
2018-06-17 21:55:39 -04:00
def searchClusterByUUID ( zk_conn , uuid ) :
2018-06-14 11:57:36 -04:00
try :
# Get the lists
2018-06-17 21:55:39 -04:00
uuid_list , name_list = getClusterDomainList ( zk_conn )
2018-06-14 11:57:36 -04:00
# 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
2018-06-17 21:55:39 -04:00
def searchClusterByName ( zk_conn , name ) :
2018-06-14 11:57:36 -04:00
try :
# Get the lists
2018-06-17 21:55:39 -04:00
uuid_list , name_list = getClusterDomainList ( zk_conn )
2018-06-14 11:57:36 -04:00
# 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
2018-06-22 12:24:53 -04:00
def verifyNode ( zk_conn , node ) :
# Verify node is valid
try :
zk_conn . get ( ' /nodes/ {} ' . format ( node ) )
except :
click . echo ( ' ERROR: No node named " {} " is present in the cluster. ' . format ( node ) )
exit ( 1 )
2018-07-18 12:15:39 -04:00
#
# Find a migration target
#
2018-07-18 02:40:20 -04:00
def findTargetHypervisor ( zk_conn , search_field , dom_uuid ) :
2018-07-18 01:53:30 -04:00
if search_field == ' mem ' :
2018-07-18 02:30:50 -04:00
return findTargetHypervisorMem ( zk_conn , dom_uuid )
2018-07-18 12:15:39 -04:00
if search_field == ' load ' :
return findTargetHypervisorLoad ( zk_conn , dom_uuid )
if search_field == ' vcpus ' :
return findTargetHypervisorVCPUs ( zk_conn , dom_uuid )
if search_field == ' vms ' :
return findTargetHypervisorVMs ( zk_conn , dom_uuid )
2018-07-18 01:53:30 -04:00
return None
2018-07-18 12:15:39 -04:00
# Get the list of valid target hypervisors
def getHypervisors ( zk_conn , dom_uuid ) :
2018-07-18 21:32:57 -04:00
valid_hypervisor_list = [ ]
2018-07-18 21:31:17 -04:00
full_hypervisor_list = zk_conn . get_children ( ' /nodes ' )
2018-07-19 11:58:33 -04:00
try :
current_hypervisor = zk_conn . get ( ' /domains/ {} /hypervisor ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
except :
current_hypervisor = None
2018-07-18 01:53:30 -04:00
2018-07-18 12:15:39 -04:00
for hypervisor in full_hypervisor_list :
2018-07-18 21:31:17 -04:00
daemon_state = zk_conn . get ( ' /nodes/ {} /daemonstate ' . format ( hypervisor ) ) [ 0 ] . decode ( ' ascii ' )
domain_state = zk_conn . get ( ' /nodes/ {} /domainstate ' . format ( hypervisor ) ) [ 0 ] . decode ( ' ascii ' )
2018-07-18 01:53:30 -04:00
if hypervisor == current_hypervisor :
continue
if daemon_state != ' run ' or domain_state != ' ready ' :
continue
2018-07-18 12:15:39 -04:00
valid_hypervisor_list . append ( hypervisor )
2018-07-18 21:48:30 -04:00
return valid_hypervisor_list
2018-07-18 01:53:30 -04:00
2018-07-18 12:15:39 -04:00
# via free memory (relative to allocated memory)
def findTargetHypervisorMem ( zk_conn , dom_uuid ) :
most_allocfree = 0
target_hypervisor = None
hypervisor_list = getHypervisors ( zk_conn , dom_uuid )
for hypervisor in hypervisor_list :
2018-07-18 21:31:17 -04:00
memalloc = int ( zk_conn . get ( ' /nodes/ {} /memalloc ' . format ( hypervisor ) ) [ 0 ] . decode ( ' ascii ' ) )
memused = int ( zk_conn . get ( ' /nodes/ {} /memused ' . format ( hypervisor ) ) [ 0 ] . decode ( ' ascii ' ) )
memfree = int ( zk_conn . get ( ' /nodes/ {} /memfree ' . format ( hypervisor ) ) [ 0 ] . decode ( ' ascii ' ) )
2018-07-18 01:53:30 -04:00
memtotal = memused + memfree
allocfree = memtotal - memalloc
if allocfree > most_allocfree :
most_allocfree = allocfree
target_hypervisor = hypervisor
return target_hypervisor
2018-07-18 12:15:39 -04:00
# via load average
def findTargetHypervisorLoad ( zk_conn , dom_uuid ) :
least_load = 9999
target_hypervisor = None
hypervisor_list = getHypervisors ( zk_conn , dom_uuid )
for hypervisor in hypervisor_list :
2018-07-18 21:36:45 -04:00
load = float ( zk_conn . get ( ' /nodes/ {} /cpuload ' . format ( hypervisor ) ) [ 0 ] . decode ( ' ascii ' ) )
2018-07-18 12:15:39 -04:00
if load < least_load :
least_load = load
2018-07-18 21:48:00 -04:00
target_hypervisor = hypervisor
2018-07-18 12:15:39 -04:00
return target_hypervisor
# via total vCPUs
def findTargetHypervisorVCPUs ( zk_conn , dom_uuid ) :
least_vcpus = 9999
target_hypervisor = None
hypervisor_list = getHypervisors ( zk_conn , dom_uuid )
for hypervisor in hypervisor_list :
2018-07-18 21:31:17 -04:00
vcpus = int ( zk_conn . get ( ' /nodes/ {} /vcpualloc ' . format ( hypervisor ) ) [ 0 ] . decode ( ' ascii ' ) )
2018-07-18 12:15:39 -04:00
if vcpus < least_vcpus :
least_vcpus = vcpus
target_hypervisor = hypervisor
return target_hypervisor
# via total VMs
def findTargetHypervisorVMs ( zk_conn , dom_uuid ) :
least_vms = 9999
target_hypervisor = None
hypervisor_list = getHypervisors ( zk_conn , dom_uuid )
for hypervisor in hypervisor_list :
2018-07-18 21:31:17 -04:00
vms = int ( zk_conn . get ( ' /nodes/ {} /domainscount ' . format ( hypervisor ) ) [ 0 ] . decode ( ' ascii ' ) )
2018-07-18 12:15:39 -04:00
if vms < least_vms :
least_vms = vms
target_hypervisor = hypervisor
return target_hypervisor
2018-06-14 11:57:36 -04:00
2018-06-05 01:39:59 -04:00
########################
########################
## ##
## CLICK COMPONENTS ##
## ##
########################
########################
2018-06-14 11:57:36 -04:00
myhostname = socket . gethostname ( )
zk_host = ' '
2018-06-11 01:35:50 -04:00
CONTEXT_SETTINGS = dict ( help_option_names = [ ' -h ' , ' --help ' ] , max_content_width = 120 )
2018-06-05 01:39:59 -04:00
###############################################################################
# pvc node
###############################################################################
2018-07-18 22:30:23 -04:00
@click.group ( name = ' node ' , short_help = ' Manage a PVC hypervisor node. ' , context_settings = CONTEXT_SETTINGS )
2018-06-05 01:39:59 -04:00
def node ( ) :
"""
Manage the state of a node in the PVC cluster .
"""
pass
###############################################################################
# pvc node flush
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' flush ' , short_help = ' Take a node out of service. ' )
2018-07-17 01:48:15 -04:00
@click.option (
' -w ' , ' --wait ' , ' wait ' , is_flag = True , default = False ,
help = ' Wait for migrations to complete before returning. '
)
2018-06-16 22:22:07 -04:00
@click.argument (
' node ' , default = myhostname
2018-06-06 20:49:07 -04:00
)
2018-07-17 01:48:15 -04:00
def flush_host ( node , wait ) :
2018-06-05 01:39:59 -04:00
"""
2018-06-16 22:22:07 -04:00
Take NODE out of active service and migrate away all VMs . If unspecified , defaults to this host .
2018-06-05 01:39:59 -04:00
"""
2018-06-06 20:49:07 -04:00
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-06 20:49:07 -04:00
2018-06-11 02:15:46 -04:00
# Verify node is valid
2018-06-22 12:24:53 -04:00
verifyNode ( zk_conn , node )
2018-06-11 02:15:46 -04:00
2018-06-16 22:22:07 -04:00
click . echo ( ' Flushing hypervisor {} of running VMs. ' . format ( node ) )
2018-06-11 02:15:46 -04:00
2018-06-06 20:49:07 -04:00
# Add the new domain to Zookeeper
2018-06-17 21:55:39 -04:00
transaction = zk_conn . transaction ( )
2018-06-16 22:22:07 -04:00
transaction . set_data ( ' /nodes/ {} /domainstate ' . format ( node ) , ' flush ' . encode ( ' ascii ' ) )
2018-06-06 20:49:07 -04:00
results = transaction . commit ( )
2018-07-17 01:48:15 -04:00
if wait == True :
while True :
2018-07-17 01:51:35 -04:00
time . sleep ( 1 )
2018-07-17 01:57:39 -04:00
node_state = zk_conn . get ( ' /nodes/ {} /domainstate ' . format ( node ) ) [ 0 ] . decode ( ' ascii ' )
2018-07-17 01:48:15 -04:00
if node_state == " flushed " :
break
2018-06-06 20:49:07 -04:00
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-05 01:39:59 -04:00
###############################################################################
2018-06-26 23:46:03 -04:00
# pvc node ready/unflush
2018-06-05 01:39:59 -04:00
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' ready ' , short_help = ' Restore node to service. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' node ' , default = myhostname
2018-06-06 20:49:07 -04:00
)
2018-06-16 22:22:07 -04:00
def ready_host ( node ) :
2018-07-18 22:58:41 -04:00
"""
Restore NODE to active service and migrate back all VMs . If unspecified , defaults to this host .
"""
2018-06-26 23:46:03 -04:00
do_ready_host ( node )
2018-07-18 22:28:49 -04:00
@click.command ( name = ' unflush ' , short_help = ' Restore node to service. ' )
2018-06-26 23:46:03 -04:00
@click.argument (
' node ' , default = myhostname
)
def unflush_host ( node ) :
2018-07-18 22:58:41 -04:00
"""
Restore NODE to active service and migrate back all VMs . If unspecified , defaults to this host .
"""
2018-06-26 23:46:03 -04:00
do_ready_host ( node )
def do_ready_host ( node ) :
2018-06-05 01:39:59 -04:00
"""
2018-06-16 22:22:07 -04:00
Restore NODE to active service and migrate back all VMs . If unspecified , defaults to this host .
2018-06-05 01:39:59 -04:00
"""
2018-06-06 20:49:07 -04:00
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-06 20:49:07 -04:00
2018-06-11 02:15:46 -04:00
# Verify node is valid
2018-06-22 12:24:53 -04:00
verifyNode ( zk_conn , node )
2018-06-11 02:15:46 -04:00
2018-06-16 22:22:07 -04:00
click . echo ( ' Restoring hypervisor {} to active service. ' . format ( node ) )
2018-06-11 02:15:46 -04:00
2018-06-06 20:49:07 -04:00
# Add the new domain to Zookeeper
2018-06-17 21:55:39 -04:00
transaction = zk_conn . transaction ( )
2018-06-16 22:22:07 -04:00
transaction . set_data ( ' /nodes/ {} /domainstate ' . format ( node ) , ' unflush ' . encode ( ' ascii ' ) )
2018-06-06 20:49:07 -04:00
results = transaction . commit ( )
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-05 01:39:59 -04:00
2018-06-11 02:49:47 -04:00
###############################################################################
# pvc node info
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' info ' , short_help = ' Show details of a node object. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' node ' , default = myhostname
2018-06-11 02:49:47 -04:00
)
@click.option (
' -l ' , ' --long ' , ' long_output ' , is_flag = True , default = False ,
help = ' Display more detailed information. '
)
2018-06-16 22:22:07 -04:00
def node_info ( node , long_output ) :
2018-06-11 02:49:47 -04:00
"""
2018-06-16 22:22:07 -04:00
Show information about node NODE . If unspecified , defaults to this host .
2018-06-11 02:49:47 -04:00
"""
2018-06-12 21:21:22 -04:00
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-12 21:21:22 -04:00
2018-06-11 12:26:51 -04:00
# Verify node is valid
2018-06-22 12:24:53 -04:00
verifyNode ( zk_conn , node )
2018-06-11 12:26:51 -04:00
2018-06-12 21:21:22 -04:00
# Get information about node in a pretty format
2018-06-17 21:55:39 -04:00
information = getInformationFromNode ( zk_conn , node , long_output )
2018-06-12 21:21:22 -04:00
if information == None :
2018-06-16 22:22:07 -04:00
click . echo ( ' ERROR: Could not find a node matching that name. ' )
2018-08-29 21:31:47 -04:00
stopZKConnection ( zk_conn )
exit ( 1 )
2018-06-12 21:21:22 -04:00
click . echo ( information )
if long_output == True :
click . echo ( ' ' )
click . echo ( ' {} Virtual machines on node: {} ' . format ( ansiiprint . bold ( ) , ansiiprint . end ( ) ) )
# List all VMs on this node
2018-07-19 21:58:11 -04:00
get_vm_list ( node , None )
2018-06-12 21:21:22 -04:00
2018-07-20 01:01:59 -04:00
click . echo ( ' ' )
2018-06-12 21:21:22 -04:00
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-11 02:49:47 -04:00
###############################################################################
# pvc node list
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' list ' , short_help = ' List all node objects. ' )
2018-07-19 21:58:11 -04:00
@click.argument (
' limit ' , default = None , required = False
)
def node_list ( limit ) :
2018-06-11 02:49:47 -04:00
"""
2018-07-19 21:58:11 -04:00
List all hypervisor nodes in the cluster ; optionally only match names matching regex LIMIT .
2018-06-11 02:49:47 -04:00
"""
2018-06-12 01:26:06 -04:00
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-12 01:26:06 -04:00
2018-07-19 21:58:11 -04:00
# Match our limit
node_list = [ ]
full_node_list = zk_conn . get_children ( ' /nodes ' )
for node in full_node_list :
if limit != None :
try :
if re . match ( limit , node ) == None :
continue
except Exception as e :
click . echo ( ' Regex Error: {} ' . format ( e ) )
exit ( 1 )
node_list . append ( node )
2018-06-12 01:26:06 -04:00
node_list_output = [ ]
node_daemon_state = { }
node_daemon_state = { }
node_domain_state = { }
node_cpu_count = { }
node_mem_used = { }
node_mem_free = { }
node_mem_total = { }
node_domains_count = { }
node_running_domains = { }
node_mem_allocated = { }
2018-07-16 12:26:28 -04:00
node_load = { }
2018-06-12 01:26:06 -04:00
# Gather information for printing
for node_name in node_list :
2018-06-17 21:55:39 -04:00
node_daemon_state [ node_name ] = zk_conn . get ( ' /nodes/ {} /daemonstate ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' )
node_domain_state [ node_name ] = zk_conn . get ( ' /nodes/ {} /domainstate ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' )
node_cpu_count [ node_name ] = zk_conn . get ( ' /nodes/ {} /staticdata ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' ) . split ( ) [ 0 ]
node_mem_used [ node_name ] = zk_conn . get ( ' /nodes/ {} /memused ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' )
node_mem_free [ node_name ] = zk_conn . get ( ' /nodes/ {} /memfree ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-12 01:26:06 -04:00
node_mem_total [ node_name ] = int ( node_mem_used [ node_name ] ) + int ( node_mem_free [ node_name ] )
2018-07-16 12:26:28 -04:00
node_load [ node_name ] = zk_conn . get ( ' /nodes/ {} /cpuload ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-17 21:55:39 -04:00
node_domains_count [ node_name ] = zk_conn . get ( ' /nodes/ {} /domainscount ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' )
node_running_domains [ node_name ] = zk_conn . get ( ' /nodes/ {} /runningdomains ' . format ( node_name ) ) [ 0 ] . decode ( ' ascii ' ) . split ( )
2018-06-12 01:26:06 -04:00
node_mem_allocated [ node_name ] = 0
for domain in node_running_domains [ node_name ] :
2018-08-20 12:03:53 -04:00
try :
parsed_xml = getDomainXML ( zk_conn , domain )
duuid , dname , dmemory , dvcpu , dvcputopo = getDomainMainDetails ( parsed_xml )
2018-08-20 13:43:00 -04:00
node_mem_allocated [ node_name ] + = int ( dmemory )
2018-08-20 12:03:53 -04:00
except AttributeError :
click . echo ( ' Error: Domain {} does not exist. ' . format ( domain ) )
2018-06-12 01:26:06 -04:00
# Determine optimal column widths
# Dynamic columns: node_name, hypervisor, migrated
node_name_length = 0
for node_name in node_list :
# node_name column
_node_name_length = len ( node_name ) + 1
if _node_name_length > node_name_length :
node_name_length = _node_name_length
# Format the string (header)
node_list_output . append (
2018-06-12 02:15:51 -04:00
' {bold} { node_name: < {node_name_length} } \
2018-06-12 02:14:50 -04:00
State : { daemon_state_colour } { node_daemon_state : < 7 } { end_colour } { domain_state_colour } { node_domain_state : < 8 } { end_colour } \
2018-07-16 12:26:28 -04:00
Resources : { node_domains_count : < 4 } { node_cpu_count : < 5 } { node_load : < 6 } \
2018-06-12 01:26:06 -04:00
RAM ( MiB ) : { node_mem_total : < 6 } { node_mem_used : < 6 } { node_mem_free : < 6 } { node_mem_allocated : < 6 } { end_bold } ' .format(
node_name_length = node_name_length ,
bold = ansiiprint . bold ( ) ,
end_bold = ansiiprint . end ( ) ,
daemon_state_colour = ' ' ,
domain_state_colour = ' ' ,
end_colour = ' ' ,
node_name = ' Name ' ,
node_daemon_state = ' Daemon ' ,
2018-06-12 02:15:51 -04:00
node_domain_state = ' Domains ' ,
2018-06-12 01:26:06 -04:00
node_domains_count = ' VMs ' ,
node_cpu_count = ' CPUs ' ,
2018-07-16 12:26:28 -04:00
node_load = ' Load ' ,
2018-06-12 01:26:06 -04:00
node_mem_total = ' Total ' ,
node_mem_used = ' Used ' ,
node_mem_free = ' Free ' ,
2018-07-16 12:26:28 -04:00
node_mem_allocated = ' VMs ' ,
2018-06-12 01:26:06 -04:00
)
)
# Format the string (elements)
for node_name in node_list :
2018-06-12 02:12:36 -04:00
if node_daemon_state [ node_name ] == ' run ' :
2018-06-11 12:26:51 -04:00
daemon_state_colour = ansiiprint . green ( )
2018-06-12 01:26:06 -04:00
elif node_daemon_state [ node_name ] == ' stop ' :
2018-06-11 12:26:51 -04:00
daemon_state_colour = ansiiprint . red ( )
2018-06-12 02:12:36 -04:00
elif node_daemon_state [ node_name ] == ' init ' :
2018-06-11 12:26:51 -04:00
daemon_state_colour = ansiiprint . yellow ( )
2018-06-12 02:12:36 -04:00
elif node_daemon_state [ node_name ] == ' dead ' :
daemon_state_colour = ansiiprint . red ( ) + ansiiprint . bold ( )
2018-06-12 02:14:06 -04:00
else :
2018-06-12 02:12:36 -04:00
daemon_state_colour = ansiiprint . blue ( )
2018-06-11 12:26:51 -04:00
2018-06-19 17:35:54 -04:00
if node_mem_allocated [ node_name ] > = node_mem_total [ node_name ] :
2018-06-19 17:36:54 -04:00
node_domain_state [ node_name ] = ' overprov '
2018-06-19 17:37:42 -04:00
domain_state_colour = ansiiprint . yellow ( )
2018-06-19 17:35:54 -04:00
elif node_domain_state [ node_name ] == ' ready ' :
domain_state_colour = ansiiprint . green ( )
2018-06-11 12:26:51 -04:00
else :
domain_state_colour = ansiiprint . blue ( )
2018-06-12 01:26:06 -04:00
node_list_output . append (
2018-06-12 02:15:51 -04:00
' {bold} { node_name: < {node_name_length} } \
2018-06-12 02:14:50 -04:00
{ daemon_state_colour } { node_daemon_state : < 7 } { end_colour } { domain_state_colour } { node_domain_state : < 8 } { end_colour } \
2018-07-16 12:26:28 -04:00
{ node_domains_count : < 4 } { node_cpu_count : < 5 } { node_load : < 6 } \
2018-06-12 01:26:06 -04:00
{ node_mem_total : < 6 } { node_mem_used : < 6 } { node_mem_free : < 6 } { node_mem_allocated : < 6 } { end_bold } ' .format(
node_name_length = node_name_length ,
bold = ' ' ,
end_bold = ' ' ,
daemon_state_colour = daemon_state_colour ,
domain_state_colour = domain_state_colour ,
end_colour = ansiiprint . end ( ) ,
node_name = node_name ,
node_daemon_state = node_daemon_state [ node_name ] ,
node_domain_state = node_domain_state [ node_name ] ,
node_domains_count = node_domains_count [ node_name ] ,
node_cpu_count = node_cpu_count [ node_name ] ,
2018-07-16 12:26:28 -04:00
node_load = node_load [ node_name ] ,
2018-06-12 01:26:06 -04:00
node_mem_total = node_mem_total [ node_name ] ,
node_mem_used = node_mem_used [ node_name ] ,
node_mem_free = node_mem_free [ node_name ] ,
node_mem_allocated = node_mem_allocated [ node_name ]
)
2018-06-11 12:26:51 -04:00
)
2018-06-12 01:26:06 -04:00
click . echo ( ' \n ' . join ( sorted ( node_list_output ) ) )
2018-06-11 12:26:51 -04:00
2018-06-12 01:26:06 -04:00
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-11 02:49:47 -04:00
2018-06-05 01:39:59 -04:00
###############################################################################
# pvc vm
###############################################################################
2018-07-18 22:30:23 -04:00
@click.group ( name = ' vm ' , short_help = ' Manage a PVC virtual machine. ' , context_settings = CONTEXT_SETTINGS )
2018-06-05 01:39:59 -04:00
def vm ( ) :
"""
Manage the state of a virtual machine in the PVC cluster .
"""
pass
###############################################################################
# pvc vm define
###############################################################################
@click.command ( name = ' define ' , short_help = ' Define a new virtual machine from a Libvirt XML file. ' )
@click.option (
2018-07-18 12:15:39 -04:00
' -t ' , ' --hypervisor ' , ' target_hypervisor ' ,
2018-07-18 22:26:31 -04:00
help = ' Home hypervisor for this domain; autodetect if unspecified. '
2018-07-18 12:15:39 -04:00
)
@click.option (
2018-07-18 22:22:12 -04:00
' -s ' , ' --selector ' , ' selector ' , default = ' mem ' , show_default = True ,
2018-07-18 12:15:39 -04:00
type = click . Choice ( [ ' mem ' , ' load ' , ' vcpus ' , ' vms ' ] ) ,
2018-07-18 22:25:12 -04:00
help = ' Method to determine optimal target hypervisor during autodetect. '
2018-06-05 01:39:59 -04:00
)
2018-06-16 22:22:07 -04:00
@click.argument (
' config ' , type = click . File ( )
)
2018-07-18 12:15:39 -04:00
def define_vm ( config , target_hypervisor , selector ) :
2018-06-05 01:39:59 -04:00
"""
2018-06-16 22:22:07 -04:00
Define a new virtual machine from Libvirt XML configuration file CONFIG .
2018-06-05 01:39:59 -04:00
"""
2018-06-06 12:00:52 -04:00
# Open the XML file
2018-06-16 22:22:07 -04:00
data = config . read ( )
config . close ( )
2018-06-06 12:00:52 -04:00
2018-06-06 12:28:15 -04:00
# Parse the XML data
2018-06-06 12:00:52 -04:00
parsed_xml = lxml . objectify . fromstring ( data )
dom_uuid = parsed_xml . uuid . text
dom_name = parsed_xml . name . text
2018-06-06 12:28:15 -04:00
click . echo ( ' Adding new VM with Name " {} " and UUID " {} " to database. ' . format ( dom_name , dom_uuid ) )
2018-06-06 12:00:52 -04:00
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-06 12:00:52 -04:00
2018-07-18 12:15:39 -04:00
if target_hypervisor == None :
target_hypervisor = findTargetHypervisor ( zk_conn , selector , dom_uuid )
2018-06-22 12:24:53 -04:00
# Verify node is valid
2018-07-16 02:22:02 -04:00
verifyNode ( zk_conn , target_hypervisor )
2018-06-22 12:24:53 -04:00
2018-06-06 12:00:52 -04:00
# Add the new domain to Zookeeper
2018-06-17 21:55:39 -04:00
transaction = zk_conn . transaction ( )
2018-06-06 13:03:02 -04:00
transaction . create ( ' /domains/ {} ' . format ( dom_uuid ) , dom_name . encode ( ' ascii ' ) )
2018-06-06 12:28:15 -04:00
transaction . create ( ' /domains/ {} /state ' . format ( dom_uuid ) , ' stop ' . encode ( ' ascii ' ) )
transaction . create ( ' /domains/ {} /hypervisor ' . format ( dom_uuid ) , target_hypervisor . encode ( ' ascii ' ) )
transaction . create ( ' /domains/ {} /lasthypervisor ' . format ( dom_uuid ) , ' ' . encode ( ' ascii ' ) )
2018-07-26 12:13:30 -04:00
transaction . create ( ' /domains/ {} /failedreason ' . format ( dom_uuid ) , ' ' . encode ( ' ascii ' ) )
2018-06-06 12:28:15 -04:00
transaction . create ( ' /domains/ {} /xml ' . format ( dom_uuid ) , data . encode ( ' ascii ' ) )
2018-06-06 12:00:52 -04:00
results = transaction . commit ( )
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-06 12:00:52 -04:00
2018-07-20 00:38:31 -04:00
###############################################################################
# pvc vm modify
###############################################################################
@click.command ( name = ' modify ' , short_help = ' Modify an existing VM configuration. ' )
@click.option (
' -e ' , ' --editor ' , ' editor ' , is_flag = True ,
help = ' Use local editor to modify existing config. '
)
@click.option (
' -r ' , ' --restart ' , ' restart ' , is_flag = True ,
help = ' Immediately restart VM to apply new config. '
)
@click.argument (
' domain '
)
@click.argument (
' config ' , type = click . File ( ) , default = None , required = False
)
def modify_vm ( domain , config , editor , restart ) :
"""
Modify existing virtual machine DOMAIN , either in - editor or with replacement CONFIG . DOMAIN may be a UUID or name .
"""
if editor == False and config == None :
click . echo ( ' Either an XML config file or the " --editor " option must be specified. ' )
exit ( 1 )
# Open a Zookeeper connection
zk_conn = startZKConnection ( zk_host )
# Validate and obtain alternate passed value
if 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 )
if dom_uuid == None :
click . echo ( ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain ) )
stopZKConnection ( zk_conn )
exit ( 1 )
# We're operating in edit-in-place mode
if editor == True :
# Grab the current config
current_vm_config = zk_conn . get ( ' /domains/ {} /xml ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
# Write it to a tempfile
fd , path = tempfile . mkstemp ( )
fw = os . fdopen ( fd , ' w ' )
fw . write ( current_vm_config )
fw . close ( )
# Edit it
editor = os . getenv ( ' EDITOR ' , ' vi ' )
subprocess . call ( ' %s %s ' % ( editor , path ) , shell = True )
# Open the tempfile to read
with open ( path , ' r ' ) as fr :
new_vm_config = fr . read ( )
fr . close ( )
# Delete the tempfile
os . unlink ( path )
# Show a diff and confirm
diff = list ( difflib . unified_diff ( current_vm_config . split ( ' \n ' ) , new_vm_config . split ( ' \n ' ) , fromfile = ' current ' , tofile = ' modified ' , fromfiledate = ' ' , tofiledate = ' ' , n = 3 , lineterm = ' ' ) )
if len ( diff ) < 1 :
click . echo ( ' Aborting with no modifications. ' )
exit ( 0 )
click . echo ( ' Pending modifications: ' )
click . echo ( ' ' )
for line in diff :
if re . match ( ' ^ \ + ' , line ) != None :
click . echo ( colorama . Fore . GREEN + line + colorama . Fore . RESET )
elif re . match ( ' ^ \ - ' , line ) != None :
click . echo ( colorama . Fore . RED + line + colorama . Fore . RESET )
elif re . match ( ' ^ \ ^ ' , line ) != None :
click . echo ( colorama . Fore . BLUE + line + colorama . Fore . RESET )
else :
click . echo ( line )
click . echo ( ' ' )
click . confirm ( ' Write modifications to Zookeeper? ' , abort = True )
click . echo ( ' Writing modified config of VM " {} " . ' . format ( dom_name ) )
# We're operating in replace mode
else :
# Open the XML file
new_vm_config = config . read ( )
config . close ( )
click . echo ( ' Replacing config of VM " {} " . ' . format ( dom_name , config ) )
# Add the modified config to Zookeeper
transaction = zk_conn . transaction ( )
transaction . set_data ( ' /domains/ {} ' . format ( dom_uuid ) , dom_name . encode ( ' ascii ' ) )
transaction . set_data ( ' /domains/ {} /xml ' . format ( dom_uuid ) , new_vm_config . encode ( ' ascii ' ) )
if restart == True :
transaction . set_data ( ' /domains/ {} /state ' . format ( dom_uuid ) , ' restart ' . encode ( ' ascii ' ) )
results = transaction . commit ( )
# Close the Zookeeper connection
stopZKConnection ( zk_conn )
2018-06-06 12:00:52 -04:00
###############################################################################
# pvc vm undefine
###############################################################################
@click.command ( name = ' undefine ' , short_help = ' Undefine and stop a virtual machine. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-06 12:00:52 -04:00
)
2018-06-16 22:22:07 -04:00
def undefine_vm ( domain ) :
2018-06-06 12:00:52 -04:00
"""
2018-06-16 22:22:07 -04:00
Stop virtual machine DOMAIN and remove it from the cluster database . DOMAIN may be a UUID or name .
2018-06-06 12:00:52 -04:00
"""
2018-06-06 12:28:15 -04:00
# Ensure at least one search method is set
2018-06-16 22:22:07 -04:00
if domain == None :
click . echo ( " ERROR: You must specify either a name or UUID value. " )
2018-08-29 21:31:47 -04:00
exit ( 1 )
2018-06-06 12:28:15 -04:00
2018-06-06 12:00:52 -04:00
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-06 12:00:52 -04:00
2018-06-16 22:22:07 -04:00
# Validate and obtain alternate passed value
if validateUUID ( domain ) :
2018-06-17 21:55:39 -04:00
dom_name = searchClusterByUUID ( zk_conn , domain )
dom_uuid = searchClusterByName ( zk_conn , dom_name )
2018-06-16 22:22:07 -04:00
else :
2018-06-17 21:55:39 -04:00
dom_uuid = searchClusterByName ( zk_conn , domain )
dom_name = searchClusterByUUID ( zk_conn , dom_uuid )
2018-06-06 12:28:15 -04:00
2018-08-09 00:29:27 -04:00
# Shut down the VM
try :
current_vm_state = zk_conn . get ( ' /domains/ {} /state ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
if current_vm_state != ' stop ' :
click . echo ( ' Forcibly stopping VM " {} " . ' . format ( dom_uuid ) )
# Set the domain into stop mode
transaction = zk_conn . transaction ( )
transaction . set_data ( ' /domains/ {} /state ' . format ( dom_uuid ) , ' stop ' . encode ( ' ascii ' ) )
transaction . commit ( )
# Wait for 3 seconds to allow state to flow to all hypervisors
click . echo ( ' Waiting for cluster to update. ' )
time . sleep ( 1 )
except :
pass
2018-06-06 12:00:52 -04:00
2018-06-15 01:37:23 -04:00
# Gracefully terminate the class instances
2018-08-09 00:29:27 -04:00
try :
click . echo ( ' Deleting VM " {} " from nodes. ' . format ( dom_uuid ) )
zk_conn . set ( ' /domains/ {} /state ' . format ( dom_uuid ) , ' delete ' . encode ( ' ascii ' ) )
time . sleep ( 5 )
except :
pass
2018-06-06 12:00:52 -04:00
# Delete the configurations
2018-08-09 00:29:27 -04:00
try :
click . echo ( ' Undefining VM " {} " . ' . format ( dom_uuid ) )
transaction = zk_conn . transaction ( )
transaction . delete ( ' /domains/ {} /state ' . format ( dom_uuid ) )
transaction . delete ( ' /domains/ {} /hypervisor ' . format ( dom_uuid ) )
transaction . delete ( ' /domains/ {} /lasthypervisor ' . format ( dom_uuid ) )
transaction . delete ( ' /domains/ {} /failedreason ' . format ( dom_uuid ) )
transaction . delete ( ' /domains/ {} /xml ' . format ( dom_uuid ) )
transaction . delete ( ' /domains/ {} ' . format ( dom_uuid ) )
transaction . commit ( )
except :
pass
2018-06-06 12:00:52 -04:00
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-05 01:39:59 -04:00
###############################################################################
# pvc vm start
###############################################################################
@click.command ( name = ' start ' , short_help = ' Start up a defined virtual machine. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-05 01:39:59 -04:00
)
2018-06-16 22:22:07 -04:00
def start_vm ( domain ) :
2018-06-05 01:39:59 -04:00
"""
2018-06-16 22:22:07 -04:00
Start virtual machine DOMAIN on its configured hypervisor . DOMAIN may be a UUID or name .
2018-06-05 01:39:59 -04:00
"""
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-05 01:39:59 -04:00
2018-06-16 22:22:07 -04:00
# Validate and obtain alternate passed value
if validateUUID ( domain ) :
2018-06-17 21:55:39 -04:00
dom_name = searchClusterByUUID ( zk_conn , domain )
dom_uuid = searchClusterByName ( zk_conn , dom_name )
2018-06-16 22:22:07 -04:00
else :
2018-06-17 21:55:39 -04:00
dom_uuid = searchClusterByName ( zk_conn , domain )
dom_name = searchClusterByUUID ( zk_conn , dom_uuid )
2018-06-05 01:39:59 -04:00
2018-06-16 22:22:07 -04:00
if dom_uuid == None :
click . echo ( ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain ) )
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-08-29 21:31:47 -04:00
exit ( 1 )
2018-06-06 12:28:15 -04:00
2018-06-05 01:39:59 -04:00
# Set the VM to start
2018-06-06 12:28:15 -04:00
click . echo ( ' Starting VM " {} " . ' . format ( dom_uuid ) )
2018-06-17 21:55:39 -04:00
zk_conn . set ( ' /domains/ %s /state ' % dom_uuid , ' start ' . encode ( ' ascii ' ) )
2018-06-05 01:39:59 -04:00
2018-06-05 22:06:08 -04:00
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-05 01:39:59 -04:00
2018-06-13 12:49:51 -04:00
###############################################################################
# pvc vm restart
###############################################################################
2018-06-17 02:24:06 -04:00
@click.command ( name = ' restart ' , short_help = ' Restart a running virtual machine. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-13 12:49:51 -04:00
)
2018-06-17 02:25:02 -04:00
def restart_vm ( domain ) :
2018-06-13 12:49:51 -04:00
"""
2018-06-17 02:24:06 -04:00
Restart running virtual machine DOMAIN . DOMAIN may be a UUID or name .
2018-06-13 12:49:51 -04:00
"""
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-13 12:49:51 -04:00
2018-06-16 22:22:07 -04:00
# Validate and obtain alternate passed value
if validateUUID ( domain ) :
2018-06-17 21:55:39 -04:00
dom_name = searchClusterByUUID ( zk_conn , domain )
dom_uuid = searchClusterByName ( zk_conn , dom_name )
2018-06-16 22:22:07 -04:00
else :
2018-06-17 21:55:39 -04:00
dom_uuid = searchClusterByName ( zk_conn , domain )
dom_name = searchClusterByUUID ( zk_conn , dom_uuid )
2018-06-13 12:49:51 -04:00
2018-06-16 22:22:07 -04:00
if dom_uuid == None :
click . echo ( ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain ) )
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-08-29 21:31:47 -04:00
exit ( 1 )
2018-06-13 12:49:51 -04:00
2018-06-14 22:45:34 -04:00
# Get state and verify we're OK to proceed
2018-06-17 21:55:39 -04:00
current_state = zk_conn . get ( ' /domains/ {} /state ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-14 23:04:32 -04:00
if current_state != ' start ' :
2018-07-18 22:26:31 -04:00
click . echo ( ' ERROR: VM " {} " is not in " start " state! ' . format ( dom_uuid ) )
2018-08-29 21:31:47 -04:00
stopZKConnection ( zk_conn )
exit ( 1 )
2018-06-14 22:45:34 -04:00
2018-06-13 12:49:51 -04:00
# Set the VM to start
click . echo ( ' Restarting VM " {} " . ' . format ( dom_uuid ) )
2018-06-17 21:55:39 -04:00
zk_conn . set ( ' /domains/ %s /state ' % dom_uuid , ' restart ' . encode ( ' ascii ' ) )
2018-06-13 12:49:51 -04:00
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-13 12:49:51 -04:00
2018-06-05 01:39:59 -04:00
###############################################################################
# pvc vm shutdown
###############################################################################
@click.command ( name = ' shutdown ' , short_help = ' Gracefully shut down a running virtual machine. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-05 01:39:59 -04:00
)
2018-06-16 22:22:07 -04:00
def shutdown_vm ( domain ) :
2018-06-05 01:39:59 -04:00
"""
2018-06-16 22:22:07 -04:00
Gracefully shut down virtual machine DOMAIN . DOMAIN may be a UUID or name .
2018-06-05 01:39:59 -04:00
"""
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-05 01:39:59 -04:00
2018-06-16 22:22:07 -04:00
# Validate and obtain alternate passed value
if validateUUID ( domain ) :
2018-06-17 21:55:39 -04:00
dom_name = searchClusterByUUID ( zk_conn , domain )
dom_uuid = searchClusterByName ( zk_conn , dom_name )
2018-06-16 22:22:07 -04:00
else :
2018-06-17 21:55:39 -04:00
dom_uuid = searchClusterByName ( zk_conn , domain )
dom_name = searchClusterByUUID ( zk_conn , dom_uuid )
2018-06-05 01:39:59 -04:00
2018-06-16 22:22:07 -04:00
if dom_uuid == None :
click . echo ( ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain ) )
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-08-29 21:31:47 -04:00
exit ( 1 )
2018-06-06 12:28:15 -04:00
2018-06-14 22:45:34 -04:00
# Get state and verify we're OK to proceed
2018-06-17 21:55:39 -04:00
current_state = zk_conn . get ( ' /domains/ {} /state ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-14 23:04:32 -04:00
if current_state != ' start ' :
2018-07-18 22:26:31 -04:00
click . echo ( ' ERROR: VM " {} " is not in " start " state! ' . format ( dom_uuid ) )
2018-08-29 21:31:47 -04:00
stopZKConnection ( zk_conn )
exit ( 1 )
2018-06-14 22:45:34 -04:00
2018-06-06 12:28:15 -04:00
# Set the VM to shutdown
click . echo ( ' Shutting down VM " {} " . ' . format ( dom_uuid ) )
2018-06-17 21:55:39 -04:00
zk_conn . set ( ' /domains/ %s /state ' % dom_uuid , ' shutdown ' . encode ( ' ascii ' ) )
2018-06-05 01:39:59 -04:00
2018-06-05 22:06:08 -04:00
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-05 01:39:59 -04:00
###############################################################################
# pvc vm stop
###############################################################################
@click.command ( name = ' stop ' , short_help = ' Forcibly halt a running virtual machine. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-05 01:39:59 -04:00
)
2018-06-16 22:22:07 -04:00
def stop_vm ( domain ) :
2018-06-05 01:39:59 -04:00
"""
2018-06-16 22:22:07 -04:00
Forcibly halt ( destroy ) running virtual machine DOMAIN . DOMAIN may be a UUID or name .
2018-06-05 01:39:59 -04:00
"""
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-05 01:39:59 -04:00
2018-06-16 22:22:07 -04:00
# Validate and obtain alternate passed value
if validateUUID ( domain ) :
2018-06-17 21:55:39 -04:00
dom_name = searchClusterByUUID ( zk_conn , domain )
dom_uuid = searchClusterByName ( zk_conn , dom_name )
2018-06-16 22:22:07 -04:00
else :
2018-06-17 21:55:39 -04:00
dom_uuid = searchClusterByName ( zk_conn , domain )
dom_name = searchClusterByUUID ( zk_conn , dom_uuid )
2018-06-05 01:39:59 -04:00
2018-06-16 22:22:07 -04:00
if dom_uuid == None :
click . echo ( ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain ) )
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-08-29 21:31:47 -04:00
exit ( 1 )
2018-06-06 12:28:15 -04:00
2018-06-14 22:45:34 -04:00
# Get state and verify we're OK to proceed
2018-06-17 21:55:39 -04:00
current_state = zk_conn . get ( ' /domains/ {} /state ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-14 23:04:32 -04:00
if current_state != ' start ' :
2018-07-18 22:26:31 -04:00
click . echo ( ' ERROR: VM " {} " is not in " start " state! ' . format ( dom_uuid ) )
2018-08-29 21:31:47 -04:00
stopZKConnection ( zk_conn )
exit ( 1 )
2018-06-14 22:45:34 -04:00
2018-06-05 01:39:59 -04:00
# Set the VM to start
2018-06-06 12:28:15 -04:00
click . echo ( ' Forcibly stopping VM " {} " . ' . format ( dom_uuid ) )
2018-06-17 21:55:39 -04:00
zk_conn . set ( ' /domains/ %s /state ' % dom_uuid , ' stop ' . encode ( ' ascii ' ) )
2018-06-05 01:39:59 -04:00
2018-06-05 22:06:08 -04:00
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-05 01:39:59 -04:00
2018-06-10 21:03:41 -04:00
###############################################################################
# pvc vm move
###############################################################################
@click.command ( name = ' move ' , short_help = ' Permanently move a virtual machine to another node. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-10 21:03:41 -04:00
)
@click.option (
2018-06-16 22:22:07 -04:00
' -t ' , ' --hypervisor ' , ' target_hypervisor ' , default = None ,
2018-07-18 22:26:31 -04:00
help = ' Target hypervisor to migrate to; autodetect if unspecified. '
2018-06-10 21:03:41 -04:00
)
2018-07-18 12:15:39 -04:00
@click.option (
2018-07-18 22:22:12 -04:00
' -s ' , ' --selector ' , ' selector ' , default = ' mem ' , show_default = True ,
2018-07-18 12:15:39 -04:00
type = click . Choice ( [ ' mem ' , ' load ' , ' vcpus ' , ' vms ' ] ) ,
2018-07-18 22:25:12 -04:00
help = ' Method to determine optimal target hypervisor during autodetect. '
2018-07-18 12:15:39 -04:00
)
def move_vm ( domain , target_hypervisor , selector ) :
2018-06-10 21:03:41 -04:00
"""
2018-06-16 22:22:07 -04:00
Permanently move virtual machine DOMAIN , via live migration if running and possible , to another hypervisor node . DOMAIN may be a UUID or name .
2018-06-10 21:03:41 -04:00
"""
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-10 21:03:41 -04:00
2018-06-16 22:22:07 -04:00
# Validate and obtain alternate passed value
if validateUUID ( domain ) :
2018-06-17 21:55:39 -04:00
dom_name = searchClusterByUUID ( zk_conn , domain )
dom_uuid = searchClusterByName ( zk_conn , dom_name )
2018-06-16 22:22:07 -04:00
else :
2018-06-17 21:55:39 -04:00
dom_uuid = searchClusterByName ( zk_conn , domain )
dom_name = searchClusterByUUID ( zk_conn , dom_uuid )
2018-06-10 21:03:41 -04:00
2018-06-16 22:22:07 -04:00
if dom_uuid == None :
click . echo ( ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain ) )
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-10 21:03:41 -04:00
return
2018-06-17 21:55:39 -04:00
current_hypervisor = zk_conn . get ( ' /domains/ {} /hypervisor ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-11 01:24:14 -04:00
2018-06-10 21:03:41 -04:00
if target_hypervisor == None :
2018-07-18 12:15:39 -04:00
target_hypervisor = findTargetHypervisor ( zk_conn , selector , dom_uuid )
2018-06-10 21:03:41 -04:00
else :
if target_hypervisor == current_hypervisor :
2018-07-18 22:26:31 -04:00
click . echo ( ' ERROR: VM " {} " is already running on hypervisor " {} " . ' . format ( dom_uuid , current_hypervisor ) )
2018-08-29 21:31:47 -04:00
stopZKConnection ( zk_conn )
exit ( 1 )
2018-06-10 21:03:41 -04:00
2018-06-22 12:24:53 -04:00
# Verify node is valid
verifyNode ( zk_conn , target_hypervisor )
2018-06-17 21:55:39 -04:00
current_vm_state = zk_conn . get ( ' /domains/ {} /state ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-10 21:03:41 -04:00
if current_vm_state == ' start ' :
click . echo ( ' Permanently migrating VM " {} " to hypervisor " {} " . ' . format ( dom_uuid , target_hypervisor ) )
2018-06-17 21:55:39 -04:00
transaction = zk_conn . transaction ( )
2018-06-10 21:03:41 -04:00
transaction . set_data ( ' /domains/ {} /state ' . format ( dom_uuid ) , ' migrate ' . encode ( ' ascii ' ) )
transaction . set_data ( ' /domains/ {} /hypervisor ' . format ( dom_uuid ) , target_hypervisor . encode ( ' ascii ' ) )
transaction . set_data ( ' /domains/ {} /lasthypervisor ' . format ( dom_uuid ) , ' ' . encode ( ' ascii ' ) )
transaction . commit ( )
else :
click . echo ( ' Permanently moving VM " {} " to hypervisor " {} " . ' . format ( dom_uuid , target_hypervisor ) )
2018-06-17 21:55:39 -04:00
transaction = zk_conn . transaction ( )
2018-06-10 21:03:41 -04:00
transaction . set_data ( ' /domains/ {} /hypervisor ' . format ( dom_uuid ) , target_hypervisor . encode ( ' ascii ' ) )
transaction . set_data ( ' /domains/ {} /lasthypervisor ' . format ( dom_uuid ) , ' ' . encode ( ' ascii ' ) )
transaction . commit ( )
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-10 21:03:41 -04:00
2018-06-05 22:06:08 -04:00
###############################################################################
# pvc vm migrate
###############################################################################
2018-06-22 12:24:53 -04:00
@click.command ( name = ' migrate ' , short_help = ' Temporarily migrate a virtual machine to another node. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-05 22:06:08 -04:00
)
@click.option (
2018-06-16 22:22:07 -04:00
' -t ' , ' --hypervisor ' , ' target_hypervisor ' , default = None ,
2018-07-18 22:26:31 -04:00
help = ' Target hypervisor to migrate to; autodetect if unspecified. '
2018-06-05 22:06:08 -04:00
)
2018-07-18 12:15:39 -04:00
@click.option (
2018-07-18 22:22:12 -04:00
' -s ' , ' --selector ' , ' selector ' , default = ' mem ' , show_default = True ,
2018-07-18 12:15:39 -04:00
type = click . Choice ( [ ' mem ' , ' load ' , ' vcpus ' , ' vms ' ] ) ,
2018-07-18 22:25:12 -04:00
help = ' Method to determine optimal target hypervisor during autodetect. '
2018-07-18 12:15:39 -04:00
)
2018-06-05 22:06:08 -04:00
@click.option (
' -f ' , ' --force ' , ' force_migrate ' , is_flag = True , default = False ,
help = ' Force migrate an already migrated VM. '
)
2018-07-18 12:15:39 -04:00
def migrate_vm ( domain , target_hypervisor , selector , force_migrate ) :
2018-06-05 01:39:59 -04:00
"""
2018-06-22 12:24:53 -04:00
Temporarily migrate running virtual machine DOMAIN , via live migration if possible , to another hypervisor node . DOMAIN may be a UUID or name . If DOMAIN is not running , it will be started on the target node .
2018-06-05 01:39:59 -04:00
"""
2018-06-05 22:06:08 -04:00
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-05 22:06:08 -04:00
2018-06-16 22:22:07 -04:00
# Validate and obtain alternate passed value
if validateUUID ( domain ) :
2018-06-17 21:55:39 -04:00
dom_name = searchClusterByUUID ( zk_conn , domain )
dom_uuid = searchClusterByName ( zk_conn , dom_name )
2018-06-16 22:22:07 -04:00
else :
2018-06-17 21:55:39 -04:00
dom_uuid = searchClusterByName ( zk_conn , domain )
dom_name = searchClusterByUUID ( zk_conn , dom_uuid )
2018-06-05 22:06:08 -04:00
2018-06-16 22:22:07 -04:00
if dom_uuid == None :
click . echo ( ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain ) )
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-08-29 21:31:47 -04:00
exit ( 1 )
2018-06-06 12:28:15 -04:00
2018-06-14 22:45:34 -04:00
# Get state and verify we're OK to proceed
2018-06-17 21:55:39 -04:00
current_state = zk_conn . get ( ' /domains/ {} /state ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-14 23:04:32 -04:00
if current_state != ' start ' :
2018-06-17 23:26:33 -04:00
target_state = ' start '
else :
target_state = ' migrate '
2018-06-14 22:45:34 -04:00
2018-06-17 21:55:39 -04:00
current_hypervisor = zk_conn . get ( ' /domains/ {} /hypervisor ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
last_hypervisor = zk_conn . get ( ' /domains/ {} /lasthypervisor ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-05 22:06:08 -04:00
if last_hypervisor != ' ' and force_migrate != True :
2018-07-18 22:26:31 -04:00
click . echo ( ' ERROR: VM " {} " has been previously migrated. ' . format ( dom_uuid ) )
2018-06-05 22:06:08 -04:00
click . echo ( ' > Last hypervisor: {} ' . format ( last_hypervisor ) )
click . echo ( ' > Current hypervisor: {} ' . format ( current_hypervisor ) )
click . echo ( ' Run `vm unmigrate` to restore the VM to its previous hypervisor, or use `--force` to override this check. ' )
2018-08-29 21:31:47 -04:00
stopZKConnection ( zk_conn )
exit ( 1 )
2018-06-05 22:06:08 -04:00
if target_hypervisor == None :
2018-07-18 12:15:39 -04:00
target_hypervisor = findTargetHypervisor ( zk_conn , selector , dom_uuid )
2018-06-05 22:06:08 -04:00
else :
if target_hypervisor == current_hypervisor :
2018-07-18 22:26:31 -04:00
click . echo ( ' ERROR: VM " {} " is already running on hypervisor " {} " . ' . format ( dom_uuid , current_hypervisor ) )
2018-08-29 21:31:47 -04:00
stopZKConnection ( zk_conn )
exit ( 1 )
2018-06-05 22:06:08 -04:00
2018-06-22 12:24:53 -04:00
# Verify node is valid
verifyNode ( zk_conn , target_hypervisor )
2018-06-05 22:06:08 -04:00
click . echo ( ' Migrating VM " {} " to hypervisor " {} " . ' . format ( dom_uuid , target_hypervisor ) )
2018-06-17 21:55:39 -04:00
transaction = zk_conn . transaction ( )
2018-06-17 23:26:33 -04:00
transaction . set_data ( ' /domains/ {} /state ' . format ( dom_uuid ) , target_state . encode ( ' ascii ' ) )
2018-06-05 22:06:08 -04:00
transaction . set_data ( ' /domains/ {} /hypervisor ' . format ( dom_uuid ) , target_hypervisor . encode ( ' ascii ' ) )
transaction . set_data ( ' /domains/ {} /lasthypervisor ' . format ( dom_uuid ) , current_hypervisor . encode ( ' ascii ' ) )
transaction . commit ( )
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-05 22:06:08 -04:00
###############################################################################
# pvc vm unmigrate
###############################################################################
2018-06-05 01:39:59 -04:00
@click.command ( name = ' unmigrate ' , short_help = ' Restore a migrated virtual machine to its original node. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-05 22:06:08 -04:00
)
2018-06-16 22:22:07 -04:00
def unmigrate_vm ( domain ) :
2018-06-05 01:39:59 -04:00
"""
2018-06-17 23:26:33 -04:00
Restore previously migrated virtual machine DOMAIN , via live migration if possible , to its original hypervisor node . DOMAIN may be a UUID or name . If DOMAIN is not running , it will be started on the target node .
2018-06-05 01:39:59 -04:00
"""
2018-06-05 22:06:08 -04:00
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-05 22:06:08 -04:00
2018-06-16 22:22:07 -04:00
# Validate and obtain alternate passed value
if validateUUID ( domain ) :
2018-06-17 21:55:39 -04:00
dom_name = searchClusterByUUID ( zk_conn , domain )
dom_uuid = searchClusterByName ( zk_conn , dom_name )
2018-06-16 22:22:07 -04:00
else :
2018-06-17 21:55:39 -04:00
dom_uuid = searchClusterByName ( zk_conn , domain )
dom_name = searchClusterByUUID ( zk_conn , dom_uuid )
2018-06-05 22:06:08 -04:00
2018-06-16 22:22:07 -04:00
if dom_uuid == None :
click . echo ( ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain ) )
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-08-29 21:31:47 -04:00
exit ( 1 )
2018-06-06 12:28:15 -04:00
2018-06-14 22:45:34 -04:00
# Get state and verify we're OK to proceed
2018-06-17 21:55:39 -04:00
current_state = zk_conn . get ( ' /domains/ {} /state ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-14 23:04:32 -04:00
if current_state != ' start ' :
2018-06-17 23:26:33 -04:00
target_state = ' start '
else :
target_state = ' migrate '
2018-06-14 22:45:34 -04:00
2018-06-17 21:55:39 -04:00
target_hypervisor = zk_conn . get ( ' /domains/ {} /lasthypervisor ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-05 22:06:08 -04:00
if target_hypervisor == ' ' :
2018-07-18 22:26:31 -04:00
click . echo ( ' ERROR: VM " {} " has not been previously migrated. ' . format ( dom_uuid ) )
2018-08-29 21:31:47 -04:00
stopZKConnection ( zk_conn )
exit ( 1 )
2018-06-05 22:06:08 -04:00
click . echo ( ' Unmigrating VM " {} " back to hypervisor " {} " . ' . format ( dom_uuid , target_hypervisor ) )
2018-06-17 21:55:39 -04:00
transaction = zk_conn . transaction ( )
2018-06-17 23:26:33 -04:00
transaction . set_data ( ' /domains/ {} /state ' . format ( dom_uuid ) , target_state . encode ( ' ascii ' ) )
2018-06-05 22:06:08 -04:00
transaction . set_data ( ' /domains/ {} /hypervisor ' . format ( dom_uuid ) , target_hypervisor . encode ( ' ascii ' ) )
transaction . set_data ( ' /domains/ {} /lasthypervisor ' . format ( dom_uuid ) , ' ' . encode ( ' ascii ' ) )
transaction . commit ( )
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-05 22:06:08 -04:00
###############################################################################
2018-06-11 01:24:14 -04:00
# pvc vm info
2018-06-05 22:06:08 -04:00
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' info ' , short_help = ' Show details of a VM object. ' )
2018-06-16 22:22:07 -04:00
@click.argument (
' domain '
2018-06-05 01:39:59 -04:00
)
2018-06-05 18:45:54 -04:00
@click.option (
' -l ' , ' --long ' , ' long_output ' , is_flag = True , default = False ,
help = ' Display more detailed information. '
)
2018-06-16 22:22:07 -04:00
def vm_info ( domain , long_output ) :
2018-06-05 01:39:59 -04:00
"""
2018-06-16 22:22:07 -04:00
Show information about virtual machine DOMAIN . DOMAIN may be a UUID or name .
2018-06-05 01:39:59 -04:00
"""
2018-06-05 22:06:08 -04:00
2018-06-16 22:22:07 -04:00
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-16 22:22:07 -04:00
# Validate and obtain alternate passed value
if validateUUID ( domain ) :
2018-06-17 21:55:39 -04:00
dom_name = searchClusterByUUID ( zk_conn , domain )
dom_uuid = searchClusterByName ( zk_conn , dom_name )
2018-06-16 22:22:07 -04:00
else :
2018-06-17 21:55:39 -04:00
dom_uuid = searchClusterByName ( zk_conn , domain )
dom_name = searchClusterByUUID ( zk_conn , dom_uuid )
2018-06-05 01:39:59 -04:00
2018-06-16 22:22:07 -04:00
if dom_uuid == None :
click . echo ( ' ERROR: Could not find VM " {} " in the cluster! ' . format ( domain ) )
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-08-29 21:31:47 -04:00
exit ( 1 )
2018-06-06 00:44:43 -04:00
2018-06-16 22:22:07 -04:00
# Gather information from XML config and print it
2018-06-17 21:55:39 -04:00
information = getInformationFromXML ( zk_conn , dom_uuid , long_output )
2018-06-05 18:45:54 -04:00
click . echo ( information )
2018-06-16 22:22:07 -04:00
2018-07-19 23:19:20 -04:00
# Get a failure reason if applicable
failedreason = zk_conn . get ( ' /domains/ {} /failedreason ' . format ( dom_uuid ) ) [ 0 ] . decode ( ' ascii ' )
if failedreason != ' ' :
click . echo ( ' ' )
click . echo ( ' {} Failure reason: {} {} ' . format ( ansiiprint . purple ( ) , ansiiprint . end ( ) , failedreason ) )
2018-07-20 01:01:59 -04:00
click . echo ( ' ' )
2018-06-16 22:22:07 -04:00
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-05 01:39:59 -04:00
2018-06-10 20:21:00 -04:00
###############################################################################
2018-06-11 01:24:14 -04:00
# pvc vm list
2018-06-10 20:21:00 -04:00
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' list ' , short_help = ' List all VM objects. ' )
2018-07-19 21:58:11 -04:00
@click.argument (
' limit ' , default = None , required = False
)
2018-06-11 01:24:14 -04:00
@click.option (
' -t ' , ' --hypervisor ' , ' hypervisor ' , default = None ,
help = ' Limit list to this hypervisor. '
)
2018-07-19 21:58:11 -04:00
def vm_list ( hypervisor , limit ) :
2018-07-18 22:58:41 -04:00
"""
2018-07-19 21:58:11 -04:00
List all virtual machines in the cluster ; optionally only match names matching regex LIMIT .
2018-07-18 22:58:41 -04:00
"""
2018-07-19 21:58:11 -04:00
get_vm_list ( hypervisor , limit )
2018-06-12 21:21:22 -04:00
# Wrapped function to allow calling from `node info`
2018-07-19 21:58:11 -04:00
def get_vm_list ( hypervisor , limit ) :
2018-06-10 20:21:00 -04:00
"""
List all virtual machines in the cluster .
"""
2018-06-12 01:26:06 -04:00
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-12 01:26:06 -04:00
2018-06-22 12:24:53 -04:00
if hypervisor != None :
# Verify node is valid
verifyNode ( zk_conn , hypervisor )
2018-06-17 21:55:39 -04:00
vm_list_raw = zk_conn . get_children ( ' /domains ' )
2018-06-12 21:44:06 -04:00
vm_list = [ ]
2018-06-12 01:26:06 -04:00
vm_list_output = [ ]
vm_hypervisor = { }
vm_state = { }
vm_migrated = { }
vm_uuid = { }
vm_name = { }
vm_memory = { }
vm_vcpu = { }
2018-06-12 21:44:06 -04:00
# If we're limited, remove other nodes' VMs
for vm in vm_list_raw :
2018-07-19 21:58:11 -04:00
# Check we don't match the limit
name = zk_conn . get ( ' /domains/ {} ' . format ( vm ) ) [ 0 ] . decode ( ' ascii ' )
if limit != None :
try :
if re . match ( limit , name ) == None :
continue
except Exception as e :
click . echo ( ' Regex Error: {} ' . format ( e ) )
exit ( 1 )
2018-06-11 01:24:14 -04:00
# Check hypervisor to avoid unneeded ZK calls
2018-06-17 21:55:39 -04:00
vm_hypervisor [ vm ] = zk_conn . get ( ' /domains/ {} /hypervisor ' . format ( vm ) ) [ 0 ] . decode ( ' ascii ' )
2018-07-19 22:01:31 -04:00
if hypervisor == None :
2018-07-19 21:58:11 -04:00
vm_list . append ( vm )
2018-06-12 21:44:06 -04:00
else :
2018-07-19 22:01:31 -04:00
if vm_hypervisor [ vm ] == hypervisor :
vm_list . append ( vm )
2018-06-11 01:24:14 -04:00
2018-06-12 21:44:06 -04:00
# Gather information for printing
for vm in vm_list :
2018-06-17 21:55:39 -04:00
vm_state [ vm ] = zk_conn . get ( ' /domains/ {} /state ' . format ( vm ) ) [ 0 ] . decode ( ' ascii ' )
vm_lasthypervisor = zk_conn . get ( ' /domains/ {} /lasthypervisor ' . format ( vm ) ) [ 0 ] . decode ( ' ascii ' )
2018-06-11 01:24:14 -04:00
if vm_lasthypervisor != ' ' :
2018-06-12 01:26:06 -04:00
vm_migrated [ vm ] = ' from {} ' . format ( vm_lasthypervisor )
2018-06-11 01:24:14 -04:00
else :
2018-06-12 01:26:06 -04:00
vm_migrated [ vm ] = ' no '
2018-06-11 01:24:14 -04:00
2018-08-20 12:03:53 -04:00
try :
vm_xml = getDomainXML ( zk_conn , vm )
vm_uuid [ vm ] , vm_name [ vm ] , vm_memory [ vm ] , vm_vcpu [ vm ] , vm_vcputopo = getDomainMainDetails ( vm_xml )
except AttributeError :
click . echo ( ' Error: Domain {} does not exist. ' . format ( domain ) )
2018-06-12 01:26:06 -04:00
# Determine optimal column widths
# Dynamic columns: node_name, hypervisor, migrated
vm_name_length = 0
vm_hypervisor_length = 0
vm_migrated_length = 0
for vm in vm_list :
# vm_name column
_vm_name_length = len ( vm_name [ vm ] ) + 1
if _vm_name_length > vm_name_length :
vm_name_length = _vm_name_length
# vm_hypervisor column
_vm_hypervisor_length = len ( vm_hypervisor [ vm ] ) + 1
if _vm_hypervisor_length > vm_hypervisor_length :
vm_hypervisor_length = _vm_hypervisor_length
# vm_migrated column
_vm_migrated_length = len ( vm_migrated [ vm ] ) + 1
if _vm_migrated_length > vm_migrated_length :
vm_migrated_length = _vm_migrated_length
# Format the string (header)
vm_list_header = ansiiprint . bold ( ) + ' Name UUID State RAM [MiB] vCPUs Hypervisor Migrated? ' + ansiiprint . end ( )
vm_list_output . append (
' {bold} { vm_name: < {vm_name_length} } {vm_uuid: <37} \
{ vm_state_colour } { vm_state : < 8 } { end_colour } \
{ vm_memory : < 10 } { vm_vcpu : < 6 } \
{ vm_hypervisor : < { vm_hypervisor_length } } \
{ vm_migrated : < { vm_migrated_length } } { end_bold } ' .format(
vm_name_length = vm_name_length ,
vm_hypervisor_length = vm_hypervisor_length ,
vm_migrated_length = vm_migrated_length ,
bold = ansiiprint . bold ( ) ,
end_bold = ansiiprint . end ( ) ,
vm_state_colour = ' ' ,
end_colour = ' ' ,
vm_name = ' Name ' ,
vm_uuid = ' UUID ' ,
vm_state = ' State ' ,
vm_memory = ' RAM (MiB) ' ,
vm_vcpu = ' vCPUs ' ,
vm_hypervisor = ' Hypervisor ' ,
vm_migrated = ' Migrated '
)
)
# Format the string (elements)
for vm in vm_list :
if vm_state [ vm ] == ' start ' :
vm_state_colour = ansiiprint . green ( )
2018-06-13 12:49:51 -04:00
elif vm_state [ vm ] == ' restart ' :
vm_state_colour = ansiiprint . yellow ( )
2018-06-13 12:58:26 -04:00
elif vm_state [ vm ] == ' shutdown ' :
vm_state_colour = ansiiprint . yellow ( )
elif vm_state [ vm ] == ' stop ' :
vm_state_colour = ansiiprint . red ( )
2018-06-14 12:16:04 -04:00
elif vm_state [ vm ] == ' failed ' :
vm_state_colour = ansiiprint . red ( )
2018-06-11 01:24:14 -04:00
else :
2018-06-12 01:26:06 -04:00
vm_state_colour = ansiiprint . blue ( )
vm_list_output . append (
' {bold} { vm_name: < {vm_name_length} } {vm_uuid: <37} \
{ vm_state_colour } { vm_state : < 8 } { end_colour } \
{ vm_memory : < 10 } { vm_vcpu : < 6 } \
{ vm_hypervisor : < { vm_hypervisor_length } } \
{ vm_migrated : < { vm_migrated_length } } { end_bold } ' .format(
vm_name_length = vm_name_length ,
vm_hypervisor_length = vm_hypervisor_length ,
vm_migrated_length = vm_migrated_length ,
bold = ' ' ,
end_bold = ' ' ,
vm_state_colour = vm_state_colour ,
end_colour = ansiiprint . end ( ) ,
vm_name = vm_name [ vm ] ,
vm_uuid = vm_uuid [ vm ] ,
vm_state = vm_state [ vm ] ,
vm_memory = vm_memory [ vm ] ,
vm_vcpu = vm_vcpu [ vm ] ,
vm_hypervisor = vm_hypervisor [ vm ] ,
vm_migrated = vm_migrated [ vm ]
)
2018-06-11 12:26:51 -04:00
)
2018-06-11 01:24:14 -04:00
2018-06-12 01:26:06 -04:00
click . echo ( ' \n ' . join ( sorted ( vm_list_output ) ) )
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-10 20:21:00 -04:00
2018-06-05 22:06:08 -04:00
###############################################################################
2018-06-06 01:07:59 -04:00
# pvc init
2018-06-05 22:06:08 -04:00
###############################################################################
2018-07-18 22:28:49 -04:00
@click.command ( name = ' init ' , short_help = ' Initialize a new cluster. ' )
2018-06-06 01:20:09 -04:00
@click.option ( ' --yes ' , is_flag = True ,
expose_value = False ,
prompt = ' DANGER: This command will destroy any existing cluster data. Do you want to continue? ' )
2018-06-06 01:07:59 -04:00
def init_cluster ( ) :
"""
2018-07-18 22:36:36 -04:00
Perform initialization of Zookeeper to act as a PVC cluster .
DANGER : This command will overwrite any existing cluster data and provision a new cluster at the specified Zookeeper connection string . Do not run this against a cluster unless you are sure this is what you want .
2018-06-06 01:07:59 -04:00
"""
2018-06-06 01:20:09 -04:00
click . echo ( ' Initializing a new cluster with Zookeeper address " {} " . ' . format ( zk_host ) )
2018-06-06 01:07:59 -04:00
# Open a Zookeeper connection
2018-06-17 21:55:39 -04:00
zk_conn = startZKConnection ( zk_host )
2018-06-06 01:07:59 -04:00
2018-06-06 01:20:09 -04:00
# Destroy the existing data
try :
2018-06-17 21:55:39 -04:00
zk_conn . delete ( ' /domains ' , recursive = True )
zk_conn . delete ( ' nodes ' , recursive = True )
2018-06-06 01:20:09 -04:00
except :
pass
2018-06-06 01:07:59 -04:00
# Create the root keys
2018-06-17 21:55:39 -04:00
transaction = zk_conn . transaction ( )
2018-06-06 01:07:59 -04:00
transaction . create ( ' /domains ' , ' ' . encode ( ' ascii ' ) )
transaction . create ( ' /nodes ' , ' ' . encode ( ' ascii ' ) )
transaction . commit ( )
# Close the Zookeeper connection
2018-06-17 21:55:39 -04:00
stopZKConnection ( zk_conn )
2018-06-06 01:07:59 -04:00
2018-06-06 01:29:11 -04:00
click . echo ( ' Successfully initialized new cluster. Any running PVC daemons will need to be restarted. ' )
2018-06-05 01:39:59 -04:00
2018-06-06 01:07:59 -04:00
###############################################################################
# pvc
###############################################################################
2018-06-05 01:39:59 -04:00
@click.group ( context_settings = CONTEXT_SETTINGS )
2018-06-06 01:07:59 -04:00
@click.option (
2018-06-11 01:35:50 -04:00
' -z ' , ' --zookeeper ' , ' _zk_host ' , envvar = ' PVC_ZOOKEEPER ' , default = ' {} :2181 ' . format ( myhostname ) , show_default = True ,
2018-06-06 01:07:59 -04:00
help = ' Zookeeper connection string. '
)
def cli ( _zk_host ) :
"""
Parallel Virtual Cluster CLI management tool
2018-06-20 14:33:40 -04:00
2018-07-18 22:30:23 -04:00
Environment variables :
" PVC_ZOOKEEPER " : Set the cluster Zookeeper address instead of using " --zookeeper " .
2018-06-06 01:07:59 -04:00
"""
2018-06-06 01:20:09 -04:00
global zk_host
2018-06-06 01:07:59 -04:00
zk_host = _zk_host
2018-06-05 01:39:59 -04:00
#
# Click command tree
#
node . add_command ( flush_host )
node . add_command ( ready_host )
2018-06-26 23:46:03 -04:00
node . add_command ( unflush_host )
2018-06-11 02:49:47 -04:00
node . add_command ( node_info )
node . add_command ( node_list )
2018-06-05 01:39:59 -04:00
vm . add_command ( define_vm )
2018-07-20 00:38:31 -04:00
vm . add_command ( modify_vm )
2018-06-06 12:28:15 -04:00
vm . add_command ( undefine_vm )
2018-06-05 01:39:59 -04:00
vm . add_command ( start_vm )
2018-06-13 12:49:51 -04:00
vm . add_command ( restart_vm )
2018-06-05 01:39:59 -04:00
vm . add_command ( shutdown_vm )
vm . add_command ( stop_vm )
2018-06-11 01:24:14 -04:00
vm . add_command ( move_vm )
2018-06-05 01:39:59 -04:00
vm . add_command ( migrate_vm )
vm . add_command ( unmigrate_vm )
2018-06-11 01:24:14 -04:00
vm . add_command ( vm_info )
vm . add_command ( vm_list )
2018-06-05 01:39:59 -04:00
cli . add_command ( node )
cli . add_command ( vm )
2018-06-06 01:20:09 -04:00
cli . add_command ( init_cluster )
2018-06-05 01:39:59 -04:00
#
# Main entry point
#
def main ( ) :
return cli ( obj = { } )
if __name__ == ' __main__ ' :
main ( )
2018-06-14 11:57:36 -04:00