193 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import sys
 | 
						|
from datetime import datetime
 | 
						|
 | 
						|
from django.core.management.base import BaseCommand
 | 
						|
from django.utils import timezone
 | 
						|
from django.contrib.auth import get_user_model
 | 
						|
 | 
						|
from opennebula.models import VM as VMModel
 | 
						|
from uncloud_vm.models import VMHost, VMProduct, VMNetworkCard, VMDiskImageProduct, VMDiskProduct
 | 
						|
 | 
						|
from uncloud_pay.models import Order
 | 
						|
 | 
						|
import logging
 | 
						|
 | 
						|
log = logging.getLogger(__name__)
 | 
						|
 | 
						|
def convert_mac_to_int(mac_address: str):
 | 
						|
    # Remove octet connecting characters
 | 
						|
    mac_address = mac_address.replace(':', '')
 | 
						|
    mac_address = mac_address.replace('.', '')
 | 
						|
    mac_address = mac_address.replace('-', '')
 | 
						|
    mac_address = mac_address.replace(' ', '')
 | 
						|
 | 
						|
    # Parse the resulting number as hexadecimal
 | 
						|
    mac_address = int(mac_address, base=16)
 | 
						|
 | 
						|
    return mac_address
 | 
						|
 | 
						|
 | 
						|
def get_vm_price(core, ram, ssd_size, hdd_size, n_of_ipv4, n_of_ipv6):
 | 
						|
    total = 3 * core + 4 * ram + (3.5 * ssd_size/10.) + (1.5 * hdd_size/100.) + 8 * n_of_ipv4 + 0 * n_of_ipv6
 | 
						|
 | 
						|
    # TODO: Find some reason about the following magical subtraction.
 | 
						|
    total -= 8
 | 
						|
 | 
						|
    return total
 | 
						|
 | 
						|
 | 
						|
def create_nics(one_vm, vm_product):
 | 
						|
    for nic in one_vm.nics:
 | 
						|
        mac_address = convert_mac_to_int(nic.get('MAC'))
 | 
						|
        ip_address = nic.get('IP', None) or nic.get('IP6_GLOBAL', None)
 | 
						|
 | 
						|
        VMNetworkCard.objects.update_or_create(
 | 
						|
            mac_address=mac_address, vm=vm_product, defaults={'ip_address': ip_address}
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
def sync_disk_and_image(one_vm, vm_product, disk_owner):
 | 
						|
    """
 | 
						|
    a) Check all opennebula disk if they are in the uncloud VM, if not add
 | 
						|
    b) Check all uncloud disks and remove them if they are not in the opennebula VM
 | 
						|
    """
 | 
						|
 | 
						|
    vmdisknum = 0
 | 
						|
 | 
						|
    one_disks_extra_data = []
 | 
						|
 | 
						|
    for disk in one_vm.disks:
 | 
						|
        vmowner = one_vm.owner
 | 
						|
        name = disk.get('image')
 | 
						|
        vmdisknum += 1
 | 
						|
 | 
						|
        log.info("Checking disk {} for VM {}".format(name, one_vm))
 | 
						|
 | 
						|
        is_os_image, is_public, status = True, False, 'active'
 | 
						|
 | 
						|
        image_size_in_gb = disk.get('image_size_in_gb')
 | 
						|
        disk_size_in_gb = disk.get('size_in_gb')
 | 
						|
        storage_class = disk.get('storage_class')
 | 
						|
        image_source = disk.get('source')
 | 
						|
        image_source_type = disk.get('source_type')
 | 
						|
 | 
						|
        image, _ = VMDiskImageProduct.objects.update_or_create(
 | 
						|
            name=name,
 | 
						|
            defaults={
 | 
						|
                'owner': disk_owner,
 | 
						|
                'is_os_image': is_os_image,
 | 
						|
                'is_public': is_public,
 | 
						|
                'size_in_gb': image_size_in_gb,
 | 
						|
                'storage_class': storage_class,
 | 
						|
                'image_source': image_source,
 | 
						|
                'image_source_type': image_source_type,
 | 
						|
                'status': status
 | 
						|
            }
 | 
						|
        )
 | 
						|
 | 
						|
        # identify vmdisk from opennebula - primary mapping key
 | 
						|
        extra_data = {
 | 
						|
            'opennebula_vm': one_vm.vmid,
 | 
						|
            'opennebula_size_in_gb': disk_size_in_gb,
 | 
						|
            'opennebula_source': disk.get('opennebula_source'),
 | 
						|
            'opennebula_disk_num': vmdisknum
 | 
						|
        }
 | 
						|
        # Save for comparing later
 | 
						|
        one_disks_extra_data.append(extra_data)
 | 
						|
 | 
						|
        try:
 | 
						|
            vm_disk = VMDiskProduct.objects.get(extra_data=extra_data)
 | 
						|
        except VMDiskProduct.DoesNotExist:
 | 
						|
            vm_disk = VMDiskProduct.objects.create(
 | 
						|
                owner=vmowner,
 | 
						|
                vm=vm_product,
 | 
						|
                image=image,
 | 
						|
                size_in_gb=disk_size_in_gb,
 | 
						|
                extra_data=extra_data
 | 
						|
            )
 | 
						|
 | 
						|
    # Now remove all disks that are not in above extra_data list
 | 
						|
    for disk in VMDiskProduct.objects.filter(vm=vm_product):
 | 
						|
        extra_data = disk.extra_data
 | 
						|
        if not extra_data in one_disks_extra_data:
 | 
						|
            log.info("Removing disk {} from VM {}".format(disk, vm_product))
 | 
						|
            disk.delete()
 | 
						|
 | 
						|
    disks = [ disk.extra_data for disk in VMDiskProduct.objects.filter(vm=vm_product) ]
 | 
						|
    log.info("VM {} has disks: {}".format(vm_product, disks))
 | 
						|
 | 
						|
class Command(BaseCommand):
 | 
						|
    help = 'Migrate Opennebula VM to regular (uncloud) vm'
 | 
						|
 | 
						|
    def add_arguments(self, parser):
 | 
						|
        parser.add_argument('--disk-owner', required=True, help="The user who owns the the opennebula disks")
 | 
						|
 | 
						|
    def handle(self, *args, **options):
 | 
						|
        log.debug("{} {}".format(args, options))
 | 
						|
 | 
						|
        disk_owner = get_user_model().objects.get(username=options['disk_owner'])
 | 
						|
 | 
						|
        for one_vm in VMModel.objects.all():
 | 
						|
 | 
						|
            if not one_vm.last_host:
 | 
						|
                log.warning("No VMHost for VM {} - VM might be on hold - skipping".format(one_vm.vmid))
 | 
						|
                continue
 | 
						|
 | 
						|
            try:
 | 
						|
                vmhost = VMHost.objects.get(hostname=one_vm.last_host)
 | 
						|
            except VMHost.DoesNotExist:
 | 
						|
                log.error("VMHost {} does not exist, aborting".format(one_vm.last_host))
 | 
						|
                raise
 | 
						|
 | 
						|
            cores = one_vm.cores
 | 
						|
            ram_in_gb = one_vm.ram_in_gb
 | 
						|
            owner = one_vm.owner
 | 
						|
            status = 'active'
 | 
						|
 | 
						|
            ssd_size = sum([ disk['size_in_gb'] for disk in one_vm.disks if disk['pool_name'] in ['ssd', 'one'] ])
 | 
						|
            hdd_size = sum([ disk['size_in_gb'] for disk in one_vm.disks if disk['pool_name'] in ['hdd'] ])
 | 
						|
 | 
						|
            # List of IPv4 addresses and Global IPv6 addresses
 | 
						|
            ipv4, ipv6 = one_vm.ips
 | 
						|
 | 
						|
            # TODO: Insert actual/real creation_date, starting_date, ending_date
 | 
						|
            #       instead of pseudo one we are putting currently
 | 
						|
            creation_date = starting_date = datetime.now(tz=timezone.utc)
 | 
						|
 | 
						|
            # Price calculation based on datacenterlight.ch
 | 
						|
            one_time_price = 0
 | 
						|
            recurring_period = 'per_month'
 | 
						|
            recurring_price = get_vm_price(cores, ram_in_gb,
 | 
						|
                                           ssd_size, hdd_size,
 | 
						|
                                           len(ipv4), len(ipv6))
 | 
						|
 | 
						|
            try:
 | 
						|
                vm_product = VMProduct.objects.get(extra_data__opennebula_id=one_vm.vmid)
 | 
						|
            except VMProduct.DoesNotExist:
 | 
						|
                order = Order.objects.create(
 | 
						|
                    owner=owner,
 | 
						|
                    creation_date=creation_date,
 | 
						|
                    starting_date=starting_date
 | 
						|
                )
 | 
						|
                vm_product = VMProduct(
 | 
						|
                    extra_data={ 'opennebula_id': one_vm.vmid },
 | 
						|
                    name=one_vm.uncloud_name,
 | 
						|
                    order=order
 | 
						|
                )
 | 
						|
 | 
						|
            # we don't use update_or_create, as filtering by json AND setting json
 | 
						|
            # at the same time does not work
 | 
						|
 | 
						|
            vm_product.vmhost = vmhost
 | 
						|
            vm_product.owner = owner
 | 
						|
            vm_product.cores = cores
 | 
						|
            vm_product.ram_in_gb = ram_in_gb
 | 
						|
            vm_product.status = status
 | 
						|
 | 
						|
            vm_product.save()
 | 
						|
 | 
						|
            # Create VMNetworkCards
 | 
						|
            create_nics(one_vm, vm_product)
 | 
						|
 | 
						|
            # Create VMDiskImageProduct and VMDiskProduct
 | 
						|
            sync_disk_and_image(one_vm, vm_product, disk_owner=disk_owner)
 |