diff --git a/pvc.py b/pvc.py index 1cb1c156..19a2b3ac 100755 --- a/pvc.py +++ b/pvc.py @@ -354,20 +354,27 @@ def verifyNode(zk_conn, node): click.echo('ERROR: No node named "{}" is present in the cluster.'.format(node)) exit(1) +# +# Find a migration target +# def findTargetHypervisor(zk_conn, search_field, dom_uuid): if search_field == 'mem': 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 -def findTargetHypervisorMem(zk_conn, dom_uuid): - # Find a target node - most_allocfree = 0 - target_hypervisor = None - - hypervisor_list = zkhandler.listchildren(zk_conn, '/nodes') +# Get the list of valid target hypervisors +def getHypervisors(zk_conn, dom_uuid): + valid_hypervisor_list = {} + full_hypervisor_list = zkhandler.listchildren(zk_conn, '/nodes') 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)) 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': 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))) memused = int(zkhandler.readdata(zk_conn, '/nodes/{}/memused'.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 +# 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.option( - '-t', '--hypervisor', 'target_hypervisor', default=myhostname, show_default=True, - help='The home hypervisor for this domain.' + '-t', '--hypervisor', 'target_hypervisor', + 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( '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. """ @@ -704,6 +772,9 @@ def define_vm(config, target_hypervisor): # Open a Zookeeper connection zk_conn = startZKConnection(zk_host) + if target_hypervisor == None: + target_hypervisor = findTargetHypervisor(zk_conn, selector, dom_uuid) + # Verify node is valid verifyNode(zk_conn, target_hypervisor) @@ -956,7 +1027,12 @@ def stop_vm(domain): '-t', '--hypervisor', 'target_hypervisor', default=None, 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. """ @@ -980,7 +1056,7 @@ def move_vm(domain, target_hypervisor): current_hypervisor = zk_conn.get('/domains/{}/hypervisor'.format(dom_uuid))[0].decode('ascii') if target_hypervisor == None: - target_hypervisor = findTargetHypervisor(zk_conn, 'mem', dom_uuid) + target_hypervisor = findTargetHypervisor(zk_conn, selector, dom_uuid) else: if target_hypervisor == 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, 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( '-f', '--force', 'force_migrate', is_flag=True, default=False, 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. """ @@ -1062,19 +1143,7 @@ def migrate_vm(domain, target_hypervisor, force_migrate): return if target_hypervisor == None: - # Determine the best hypervisor to migrate the VM to based on active memory usage - 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 + target_hypervisor = findTargetHypervisor(zk_conn, selector, dom_uuid) else: if target_hypervisor == current_hypervisor: click.echo('ERROR: The VM "{}" is already running on hypervisor "{}".'.format(dom_uuid, current_hypervisor))