Implement migration targets in client

Addresses #11
This commit is contained in:
Joshua Boniface 2018-07-18 12:15:39 -04:00
parent eb23488486
commit f91e210f1a
1 changed files with 95 additions and 26 deletions

121
pvc.py
View File

@ -354,20 +354,27 @@ def verifyNode(zk_conn, node):
click.echo('ERROR: No node named "{}" is present in the cluster.'.format(node)) click.echo('ERROR: No node named "{}" is present in the cluster.'.format(node))
exit(1) exit(1)
#
# Find a migration target
#
def findTargetHypervisor(zk_conn, search_field, dom_uuid): def findTargetHypervisor(zk_conn, search_field, dom_uuid):
if search_field == 'mem': if search_field == 'mem':
return findTargetHypervisorMem(zk_conn, dom_uuid) return findTargetHypervisorMem(zk_conn, dom_uuid)
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)
return None return None
def findTargetHypervisorMem(zk_conn, dom_uuid): # Get the list of valid target hypervisors
# Find a target node def getHypervisors(zk_conn, dom_uuid):
most_allocfree = 0 valid_hypervisor_list = {}
target_hypervisor = None full_hypervisor_list = zkhandler.listchildren(zk_conn, '/nodes')
hypervisor_list = zkhandler.listchildren(zk_conn, '/nodes')
current_hypervisor = zkhandler.readdata(zk_conn, '/domains/{}/hypervisor'.format(dom_uuid)) current_hypervisor = zkhandler.readdata(zk_conn, '/domains/{}/hypervisor'.format(dom_uuid))
for hypervisor in hypervisor_list: for hypervisor in full_hypervisor_list:
daemon_state = zkhandler.readdata(zk_conn, '/nodes/{}/daemonstate'.format(hypervisor)) daemon_state = zkhandler.readdata(zk_conn, '/nodes/{}/daemonstate'.format(hypervisor))
domain_state = zkhandler.readdata(zk_conn, '/nodes/{}/domainstate'.format(hypervisor)) domain_state = zkhandler.readdata(zk_conn, '/nodes/{}/domainstate'.format(hypervisor))
@ -376,7 +383,18 @@ def findTargetHypervisorMem(zk_conn, dom_uuid):
if daemon_state != 'run' or domain_state != 'ready': if daemon_state != 'run' or domain_state != 'ready':
continue continue
valid_hypervisor_list.append(hypervisor)
return full_hypervisor_list
# 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:
memalloc = int(zkhandler.readdata(zk_conn, '/nodes/{}/memalloc'.format(hypervisor))) memalloc = int(zkhandler.readdata(zk_conn, '/nodes/{}/memalloc'.format(hypervisor)))
memused = int(zkhandler.readdata(zk_conn, '/nodes/{}/memused'.format(hypervisor))) memused = int(zkhandler.readdata(zk_conn, '/nodes/{}/memused'.format(hypervisor)))
memfree = int(zkhandler.readdata(zk_conn, '/nodes/{}/memfree'.format(hypervisor))) memfree = int(zkhandler.readdata(zk_conn, '/nodes/{}/memfree'.format(hypervisor)))
@ -389,6 +407,51 @@ def findTargetHypervisorMem(zk_conn, dom_uuid):
return target_hypervisor return target_hypervisor
# 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:
load = int(zkhandler.readdata(zk_conn, '/nodes/{}/load'.format(hypervisor)))
if load < least_load:
least_load = load
target_hypevisor = hypervisor
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:
vcpus = int(zkhandler.readdata(zk_conn, '/nodes/{}/vcpualloc'.format(hypervisor)))
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:
vms = int(zkhandler.readdata(zk_conn, '/nodes/{}/domainscount'.format(hypervisor)))
if vms < least_vms:
least_vms = vms
target_hypervisor = hypervisor
return target_hypervisor
######################## ########################
######################## ########################
@ -680,13 +743,18 @@ def vm():
############################################################################### ###############################################################################
@click.command(name='define', short_help='Define a new virtual machine from a Libvirt XML file.') @click.command(name='define', short_help='Define a new virtual machine from a Libvirt XML file.')
@click.option( @click.option(
'-t', '--hypervisor', 'target_hypervisor', default=myhostname, show_default=True, '-t', '--hypervisor', 'target_hypervisor',
help='The home hypervisor for this domain.' help='The home hypervisor for this domain; autoselects if unspecified.'
)
@click.option(
'-s', '--selector', 'selector', default='mem',
type=click.Choice(['mem','load','vcpus','vms']),
help='Method to determine the optimal target hypervisor automatically.'
) )
@click.argument( @click.argument(
'config', type=click.File() 'config', type=click.File()
) )
def define_vm(config, target_hypervisor): def define_vm(config, target_hypervisor, selector):
""" """
Define a new virtual machine from Libvirt XML configuration file CONFIG. Define a new virtual machine from Libvirt XML configuration file CONFIG.
""" """
@ -704,6 +772,9 @@ def define_vm(config, target_hypervisor):
# Open a Zookeeper connection # Open a Zookeeper connection
zk_conn = startZKConnection(zk_host) zk_conn = startZKConnection(zk_host)
if target_hypervisor == None:
target_hypervisor = findTargetHypervisor(zk_conn, selector, dom_uuid)
# Verify node is valid # Verify node is valid
verifyNode(zk_conn, target_hypervisor) verifyNode(zk_conn, target_hypervisor)
@ -956,7 +1027,12 @@ def stop_vm(domain):
'-t', '--hypervisor', 'target_hypervisor', default=None, '-t', '--hypervisor', 'target_hypervisor', default=None,
help='The target hypervisor to migrate to. Autodetect based on most free RAM if unspecified.' help='The target hypervisor to migrate to. Autodetect based on most free RAM if unspecified.'
) )
def move_vm(domain, target_hypervisor): @click.option(
'-s', '--selector', 'selector', default='mem',
type=click.Choice(['mem','load','vcpus','vms']),
help='Method to determine the optimal target hypervisor automatically.'
)
def move_vm(domain, target_hypervisor, selector):
""" """
Permanently move virtual machine DOMAIN, via live migration if running and possible, to another hypervisor node. DOMAIN may be a UUID or name. Permanently move virtual machine DOMAIN, via live migration if running and possible, to another hypervisor node. DOMAIN may be a UUID or name.
""" """
@ -980,7 +1056,7 @@ def move_vm(domain, target_hypervisor):
current_hypervisor = zk_conn.get('/domains/{}/hypervisor'.format(dom_uuid))[0].decode('ascii') current_hypervisor = zk_conn.get('/domains/{}/hypervisor'.format(dom_uuid))[0].decode('ascii')
if target_hypervisor == None: if target_hypervisor == None:
target_hypervisor = findTargetHypervisor(zk_conn, 'mem', dom_uuid) target_hypervisor = findTargetHypervisor(zk_conn, selector, dom_uuid)
else: else:
if target_hypervisor == current_hypervisor: if target_hypervisor == current_hypervisor:
click.echo('ERROR: The VM "{}" is already running on hypervisor "{}".'.format(dom_uuid, current_hypervisor)) click.echo('ERROR: The VM "{}" is already running on hypervisor "{}".'.format(dom_uuid, current_hypervisor))
@ -1019,11 +1095,16 @@ def move_vm(domain, target_hypervisor):
'-t', '--hypervisor', 'target_hypervisor', default=None, '-t', '--hypervisor', 'target_hypervisor', default=None,
help='The target hypervisor to migrate to. Autodetect based on most free RAM if unspecified.' help='The target hypervisor to migrate to. Autodetect based on most free RAM if unspecified.'
) )
@click.option(
'-s', '--selector', 'selector', default='mem',
type=click.Choice(['mem','load','vcpus','vms']),
help='Method to determine the optimal target hypervisor automatically.'
)
@click.option( @click.option(
'-f', '--force', 'force_migrate', is_flag=True, default=False, '-f', '--force', 'force_migrate', is_flag=True, default=False,
help='Force migrate an already migrated VM.' help='Force migrate an already migrated VM.'
) )
def migrate_vm(domain, target_hypervisor, force_migrate): def migrate_vm(domain, target_hypervisor, selector, force_migrate):
""" """
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. 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.
""" """
@ -1062,19 +1143,7 @@ def migrate_vm(domain, target_hypervisor, force_migrate):
return return
if target_hypervisor == None: if target_hypervisor == None:
# Determine the best hypervisor to migrate the VM to based on active memory usage target_hypervisor = findTargetHypervisor(zk_conn, selector, dom_uuid)
hypervisor_list = zk_conn.get_children('/nodes')
most_memfree = 0
for hypervisor in hypervisor_list:
daemon_state = zk_conn.get('/nodes/{}/daemonstate'.format(hypervisor))[0].decode('ascii')
domain_state = zk_conn.get('/nodes/{}/domainstate'.format(hypervisor))[0].decode('ascii')
if daemon_state != 'run' or domain_state != 'ready' or hypervisor == current_hypervisor:
continue
memfree = int(zk_conn.get('/nodes/{}/memfree'.format(hypervisor))[0].decode('ascii'))
if memfree > most_memfree:
most_memfree = memfree
target_hypervisor = hypervisor
else: else:
if target_hypervisor == current_hypervisor: if target_hypervisor == current_hypervisor:
click.echo('ERROR: The VM "{}" is already running on hypervisor "{}".'.format(dom_uuid, current_hypervisor)) click.echo('ERROR: The VM "{}" is already running on hypervisor "{}".'.format(dom_uuid, current_hypervisor))