118 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			118 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
import bitmath
 | 
						|
 | 
						|
from collections import Counter
 | 
						|
from functools import reduce
 | 
						|
 | 
						|
from ucloud_common.vm import VmPool, VMStatus
 | 
						|
from ucloud_common.host import HostPool, HostStatus
 | 
						|
from ucloud_common.request import RequestEntry, RequestPool, RequestType
 | 
						|
 | 
						|
from decouple import config
 | 
						|
from config import etcd_client as client
 | 
						|
 | 
						|
vm_pool = VmPool(client, config("VM_PREFIX"))
 | 
						|
host_pool = HostPool(client, config("HOST_PREFIX"))
 | 
						|
request_pool = RequestPool(client, config("REQUEST_PREFIX"))
 | 
						|
 | 
						|
 | 
						|
def accumulated_specs(vms_specs):    
 | 
						|
    if not vms_specs:
 | 
						|
        return {}
 | 
						|
    return reduce((lambda x, y: Counter(x) + Counter(y)), vms_specs)
 | 
						|
 | 
						|
 | 
						|
def remaining_resources(host_specs, vms_specs):
 | 
						|
    # Return remaining resources host_specs - vms
 | 
						|
    
 | 
						|
    _vms_specs = Counter(vms_specs)
 | 
						|
    _remaining = Counter(host_specs)
 | 
						|
 | 
						|
    for component in _vms_specs:
 | 
						|
        if isinstance(_vms_specs[component], str):
 | 
						|
            _vms_specs[component] = int(bitmath.parse_string(_vms_specs[component]).to_MB())
 | 
						|
        elif isinstance(_vms_specs[component], list):
 | 
						|
            _vms_specs[component] = map(lambda x: int(bitmath.parse_string(x).to_MB()), _vms_specs[component])
 | 
						|
            _vms_specs[component] = reduce(lambda x, y: x + y, _vms_specs[component], 0)
 | 
						|
 | 
						|
    for component in _remaining:
 | 
						|
        if isinstance(_remaining[component], str):
 | 
						|
            _remaining[component] = int(bitmath.parse_string(_remaining[component]).to_MB())
 | 
						|
        elif isinstance(_remaining[component], list):
 | 
						|
            _remaining[component] = map(lambda x: int(bitmath.parse_string(x).to_MB()), _remaining[component])
 | 
						|
            _remaining[component] = reduce(lambda x, y: x + y, _remaining[component], 0)
 | 
						|
 | 
						|
    _remaining.subtract(_vms_specs)
 | 
						|
 | 
						|
    return _remaining
 | 
						|
 | 
						|
 | 
						|
class NoSuitableHostFound(Exception):
 | 
						|
    """Exception when no host found that can host a VM."""
 | 
						|
 | 
						|
 | 
						|
def get_suitable_host(vm_specs, hosts=None):
 | 
						|
    if hosts is None:
 | 
						|
        hosts = host_pool.by_status(HostStatus.alive)
 | 
						|
 | 
						|
    for host in hosts:
 | 
						|
        # Filter them by host_name
 | 
						|
        vms = vm_pool.by_host(host.key)
 | 
						|
 | 
						|
        # Filter them by status
 | 
						|
        vms = vm_pool.by_status(VMStatus.running, vms)
 | 
						|
 | 
						|
        running_vms_specs = [vm.specs for vm in vms]
 | 
						|
 | 
						|
        # Accumulate all of their combined specs
 | 
						|
        running_vms_accumulated_specs = accumulated_specs(running_vms_specs)
 | 
						|
 | 
						|
        # Find out remaining resources after
 | 
						|
        # host_specs - already running vm_specs
 | 
						|
        remaining = remaining_resources(host.specs, running_vms_accumulated_specs)
 | 
						|
       
 | 
						|
        # Find out remaining - new_vm_specs
 | 
						|
        remaining = remaining_resources(remaining, vm_specs)
 | 
						|
 | 
						|
        if all(map(lambda x: x >= 0, remaining.values())):
 | 
						|
            return host.key
 | 
						|
 | 
						|
    raise NoSuitableHostFound
 | 
						|
 | 
						|
 | 
						|
def dead_host_detection():
 | 
						|
    # Bring out your dead! - Monty Python and the Holy Grail
 | 
						|
    hosts = host_pool.by_status(HostStatus.alive)
 | 
						|
    dead_hosts_keys = []
 | 
						|
 | 
						|
    for host in hosts:
 | 
						|
        # Only check those who claims to be alive
 | 
						|
        if host.status == HostStatus.alive:
 | 
						|
            if not host.is_alive():
 | 
						|
                dead_hosts_keys.append(host.key)
 | 
						|
 | 
						|
    return dead_hosts_keys
 | 
						|
 | 
						|
 | 
						|
def dead_host_mitigation(dead_hosts_keys):
 | 
						|
    for host_key in dead_hosts_keys:
 | 
						|
        host = host_pool.get(host_key)
 | 
						|
        host.declare_dead()
 | 
						|
 | 
						|
        vms_hosted_on_dead_host = vm_pool.by_host(host_key)
 | 
						|
        for vm in vms_hosted_on_dead_host:
 | 
						|
            vm.declare_killed()
 | 
						|
            vm_pool.put(vm)
 | 
						|
        host_pool.put(host)
 | 
						|
 | 
						|
 | 
						|
def assign_host(vm):
 | 
						|
    vm.hostname = get_suitable_host(vm.specs)
 | 
						|
    vm_pool.put(vm)
 | 
						|
 | 
						|
    r = RequestEntry.from_scratch(type=RequestType.StartVM,
 | 
						|
                                  uuid=vm.uuid,
 | 
						|
                                  hostname=vm.hostname)
 | 
						|
    request_pool.put(r)
 | 
						|
 | 
						|
    vm.log.append("VM scheduled for starting")
 | 
						|
    return vm.hostname
 |