Merge remote-tracking branch 'ahmed/migrate-one-to-regular-vm'
This commit is contained in:
commit
14a4fa8cc1
11 changed files with 370 additions and 8 deletions
|
@ -0,0 +1,139 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from opennebula.models import VM as VMModel
|
||||||
|
from uncloud_vm.models import VMHost, VMProduct, VMNetworkCard, VMDiskImageProduct, VMDiskProduct
|
||||||
|
from uncloud_pay.models import Order
|
||||||
|
|
||||||
|
|
||||||
|
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, storage, n_of_ipv4, n_of_ipv6):
|
||||||
|
storage = storage / 10 # Division by 10 because our base storage unit is 10 GB
|
||||||
|
total = 3 * core + 4 * ram + 3.5 * storage + 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 create_disk_and_image(one_vm, vm_product):
|
||||||
|
for disk in one_vm.disks:
|
||||||
|
owner = one_vm.owner
|
||||||
|
name = disk.get('image')
|
||||||
|
|
||||||
|
# TODO: Fix the following hard coded values
|
||||||
|
is_os_image, is_public, status = True, True, 'active'
|
||||||
|
|
||||||
|
image_size_in_gb = disk.get('image_size_in_gb')
|
||||||
|
disk_size_in_gb = disk.get('size_in_gb')
|
||||||
|
storage_class = disk.get('pool_name')
|
||||||
|
image_source = disk.get('source')
|
||||||
|
image_source_type = disk.get('source_type')
|
||||||
|
|
||||||
|
image, _ = VMDiskImageProduct.objects.update_or_create(
|
||||||
|
name=name,
|
||||||
|
defaults={
|
||||||
|
'owner': 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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
VMDiskProduct.objects.update_or_create(
|
||||||
|
owner=owner, vm=vm_product,
|
||||||
|
defaults={
|
||||||
|
'image': image,
|
||||||
|
'size_in_gb': disk_size_in_gb
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Migrate Opennebula VM to regular (uncloud) vm'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
for one_vm in VMModel.objects.all():
|
||||||
|
# Host on which the VM is currently residing
|
||||||
|
host = VMHost.objects.filter(vms__icontains=one_vm.vmid).first()
|
||||||
|
|
||||||
|
# VCPU, RAM, Owner, Status
|
||||||
|
# TODO: Set actual status instead of hard coded 'active'
|
||||||
|
vm_id, cores, ram_in_gb = one_vm.vmid, one_vm.cores, one_vm.ram_in_gb
|
||||||
|
owner, status = one_vm.owner, 'active'
|
||||||
|
|
||||||
|
# Total Amount of SSD Storage
|
||||||
|
# TODO: What would happen if the attached storage is not SSD but HDD?
|
||||||
|
total_storage_in_gb = sum([disk['size_in_gb'] for disk in one_vm.disks])
|
||||||
|
|
||||||
|
# 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 = ending_date = datetime.now(tz=timezone.utc)
|
||||||
|
|
||||||
|
# Price calculation
|
||||||
|
|
||||||
|
# TODO: Make the following non-hardcoded
|
||||||
|
one_time_price = 0
|
||||||
|
recurring_period = 'per_month'
|
||||||
|
|
||||||
|
recurring_price = get_vm_price(cores, ram_in_gb, total_storage_in_gb, len(ipv4), len(ipv6))
|
||||||
|
try:
|
||||||
|
vm_product = VMProduct.objects.get(vmid=vm_id)
|
||||||
|
except VMProduct.DoesNotExist:
|
||||||
|
order = Order.objects.create(
|
||||||
|
owner=one_vm.owner,
|
||||||
|
creation_date=creation_date,
|
||||||
|
starting_date=starting_date,
|
||||||
|
ending_date=ending_date,
|
||||||
|
one_time_price=one_time_price,
|
||||||
|
recurring_price=recurring_price,
|
||||||
|
recurring_period=recurring_period
|
||||||
|
)
|
||||||
|
vm_product, _ = VMProduct.objects.update_or_create(
|
||||||
|
vmid=vm_id,
|
||||||
|
defaults={
|
||||||
|
'cores': cores,
|
||||||
|
'ram_in_gb': ram_in_gb,
|
||||||
|
'owner': owner,
|
||||||
|
'vmhost': host,
|
||||||
|
'order': order,
|
||||||
|
'status': status
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create VMNetworkCards
|
||||||
|
create_nics(one_vm, vm_product)
|
||||||
|
|
||||||
|
# Create VMDiskImageProduct and VMDiskProduct
|
||||||
|
create_disk_and_image(one_vm, vm_product)
|
74
uncloud/opennebula/management/commands/synchost.py
Normal file
74
uncloud/opennebula/management/commands/synchost.py
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
import uncloud.secrets as secrets
|
||||||
|
|
||||||
|
from xmlrpc.client import ServerProxy as RPCClient
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from xmltodict import parse
|
||||||
|
from enum import IntEnum
|
||||||
|
from opennebula.models import VM as VMModel
|
||||||
|
from uncloud_vm.models import VMHost
|
||||||
|
from django_auth_ldap.backend import LDAPBackend
|
||||||
|
|
||||||
|
|
||||||
|
class HostStates(IntEnum):
|
||||||
|
"""
|
||||||
|
The following flags are copied from
|
||||||
|
https://docs.opennebula.org/5.8/integration/system_interfaces/api.html#schemas-for-host
|
||||||
|
"""
|
||||||
|
INIT = 0 # Initial state for enabled hosts
|
||||||
|
MONITORING_MONITORED = 1 # Monitoring the host (from monitored)
|
||||||
|
MONITORED = 2 # The host has been successfully monitored
|
||||||
|
ERROR = 3 # An error ocurrer while monitoring the host
|
||||||
|
DISABLED = 4 # The host is disabled
|
||||||
|
MONITORING_ERROR = 5 # Monitoring the host (from error)
|
||||||
|
MONITORING_INIT = 6 # Monitoring the host (from init)
|
||||||
|
MONITORING_DISABLED = 7 # Monitoring the host (from disabled)
|
||||||
|
OFFLINE = 8 # The host is totally offline
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Syncronize Host information from OpenNebula'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
with RPCClient(secrets.OPENNEBULA_URL) as rpc_client:
|
||||||
|
success, response, *_ = rpc_client.one.hostpool.info(secrets.OPENNEBULA_USER_PASS)
|
||||||
|
if success:
|
||||||
|
response = json.loads(json.dumps(parse(response)))
|
||||||
|
host_pool = response.get('HOST_POOL', {}).get('HOST', {})
|
||||||
|
for host in host_pool:
|
||||||
|
host_share = host.get('HOST_SHARE', {})
|
||||||
|
|
||||||
|
host_name = host.get('NAME')
|
||||||
|
state = int(host.get('STATE', HostStates.OFFLINE.value))
|
||||||
|
|
||||||
|
if state == HostStates.MONITORED:
|
||||||
|
status = 'active'
|
||||||
|
elif state == HostStates.DISABLED:
|
||||||
|
status = 'disabled'
|
||||||
|
else:
|
||||||
|
status = 'unusable'
|
||||||
|
|
||||||
|
usable_cores = host_share.get('TOTAL_CPU')
|
||||||
|
usable_ram_in_kb = int(host_share.get('TOTAL_MEM', 0))
|
||||||
|
usable_ram_in_gb = int(usable_ram_in_kb / 2 ** 20)
|
||||||
|
|
||||||
|
vms = host.get('VMS', {}) or {}
|
||||||
|
vms = vms.get('ID', []) or []
|
||||||
|
vms = ','.join(vms)
|
||||||
|
|
||||||
|
VMHost.objects.update_or_create(
|
||||||
|
hostname=host_name,
|
||||||
|
defaults={
|
||||||
|
'usable_cores': usable_cores,
|
||||||
|
'usable_ram_in_gb': usable_ram_in_gb,
|
||||||
|
'status': status,
|
||||||
|
'vms': vms
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(response)
|
|
@ -5,14 +5,12 @@ import uncloud.secrets as secrets
|
||||||
|
|
||||||
from xmlrpc.client import ServerProxy as RPCClient
|
from xmlrpc.client import ServerProxy as RPCClient
|
||||||
|
|
||||||
|
from django_auth_ldap.backend import LDAPBackend
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from xmltodict import parse
|
from xmltodict import parse
|
||||||
|
|
||||||
from opennebula.models import VM as VMModel
|
from opennebula.models import VM as VMModel
|
||||||
|
|
||||||
from django_auth_ldap.backend import LDAPBackend
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Syncronize VM information from OpenNebula'
|
help = 'Syncronize VM information from OpenNebula'
|
||||||
|
|
|
@ -19,7 +19,7 @@ class VM(models.Model):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ram_in_gb(self):
|
def ram_in_gb(self):
|
||||||
return (int(self.data['TEMPLATE']['MEMORY'])/1024.)
|
return int(self.data['TEMPLATE']['MEMORY'])/1024
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def disks(self):
|
def disks(self):
|
||||||
|
@ -41,9 +41,15 @@ class VM(models.Model):
|
||||||
|
|
||||||
disks = [
|
disks = [
|
||||||
{
|
{
|
||||||
'size_in_gb': int(d['SIZE'])/1024. ,
|
'size_in_gb': int(d['SIZE'])/1024,
|
||||||
'opennebula_source': d['SOURCE'],
|
'opennebula_source': d['SOURCE'],
|
||||||
'opennebula_name': d['IMAGE'],
|
'opennebula_name': d['IMAGE'],
|
||||||
|
'image_size_in_gb': int(d['ORIGINAL_SIZE'])/1024,
|
||||||
|
'pool_name': d['POOL_NAME'],
|
||||||
|
'image': d['IMAGE'],
|
||||||
|
'source': d['SOURCE'],
|
||||||
|
'source_type': d['TM_MAD']
|
||||||
|
|
||||||
}
|
}
|
||||||
for d in disks
|
for d in disks
|
||||||
]
|
]
|
||||||
|
@ -57,3 +63,22 @@ class VM(models.Model):
|
||||||
@property
|
@property
|
||||||
def graphics(self):
|
def graphics(self):
|
||||||
return self.data.get('TEMPLATE', {}).get('GRAPHICS', {})
|
return self.data.get('TEMPLATE', {}).get('GRAPHICS', {})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nics(self):
|
||||||
|
_nics = self.data.get('TEMPLATE', {}).get('NIC', {})
|
||||||
|
if isinstance(_nics, dict):
|
||||||
|
_nics = [_nics]
|
||||||
|
return _nics
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ips(self):
|
||||||
|
ipv4, ipv6 = [], []
|
||||||
|
for nic in self.nics:
|
||||||
|
ip = nic.get('IP')
|
||||||
|
ip6 = nic.get('IP6_GLOBAL')
|
||||||
|
if ip:
|
||||||
|
ipv4.append(ip)
|
||||||
|
if ip6:
|
||||||
|
ipv6.append(ip6)
|
||||||
|
return ipv4, ipv6
|
||||||
|
|
23
uncloud/uncloud_vm/migrations/0009_auto_20200303_0927.py
Normal file
23
uncloud/uncloud_vm/migrations/0009_auto_20200303_0927.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.0.3 on 2020-03-03 09:27
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('uncloud_vm', '0008_auto_20200229_1611'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vmhost',
|
||||||
|
name='vms',
|
||||||
|
field=models.TextField(default=''),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vmdiskproduct',
|
||||||
|
name='size_in_gb',
|
||||||
|
field=models.FloatField(blank=True),
|
||||||
|
),
|
||||||
|
]
|
18
uncloud/uncloud_vm/migrations/0010_auto_20200303_1208.py
Normal file
18
uncloud/uncloud_vm/migrations/0010_auto_20200303_1208.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.0.3 on 2020-03-03 12:08
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('uncloud_vm', '0009_auto_20200303_0927'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vmnetworkcard',
|
||||||
|
name='mac_address',
|
||||||
|
field=models.BigIntegerField(),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.0.3 on 2020-03-03 18:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('uncloud_vm', '0010_auto_20200303_1208'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vmdiskimageproduct',
|
||||||
|
name='source_type',
|
||||||
|
field=models.CharField(max_length=128, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.0.3 on 2020-03-03 18:35
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('uncloud_vm', '0011_vmdiskimageproduct_source_type'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vmdiskimageproduct',
|
||||||
|
name='source',
|
||||||
|
field=models.CharField(max_length=128, null=True),
|
||||||
|
),
|
||||||
|
]
|
23
uncloud/uncloud_vm/migrations/0013_auto_20200303_1845.py
Normal file
23
uncloud/uncloud_vm/migrations/0013_auto_20200303_1845.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.0.3 on 2020-03-03 18:45
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('uncloud_vm', '0012_vmdiskimageproduct_source'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='vmdiskimageproduct',
|
||||||
|
old_name='source',
|
||||||
|
new_name='image_source',
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='vmdiskimageproduct',
|
||||||
|
old_name='source_type',
|
||||||
|
new_name='image_source_type',
|
||||||
|
),
|
||||||
|
]
|
18
uncloud/uncloud_vm/migrations/0014_vmproduct_vmid.py
Normal file
18
uncloud/uncloud_vm/migrations/0014_vmproduct_vmid.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.0.3 on 2020-03-04 07:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('uncloud_vm', '0013_auto_20200303_1845'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vmproduct',
|
||||||
|
name='vmid',
|
||||||
|
field=models.IntegerField(null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -40,6 +40,12 @@ class VMHost(models.Model):
|
||||||
max_length=32, choices=STATUS_CHOICES, default=STATUS_DEFAULT
|
max_length=32, choices=STATUS_CHOICES, default=STATUS_DEFAULT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# List of VMs running on this host
|
||||||
|
vms = models.TextField(default='')
|
||||||
|
|
||||||
|
def get_vms(self):
|
||||||
|
return self.vms.split(',')
|
||||||
|
|
||||||
|
|
||||||
class VMProduct(Product):
|
class VMProduct(Product):
|
||||||
vmhost = models.ForeignKey(
|
vmhost = models.ForeignKey(
|
||||||
|
@ -51,6 +57,7 @@ class VMProduct(Product):
|
||||||
name = models.CharField(max_length=32)
|
name = models.CharField(max_length=32)
|
||||||
cores = models.IntegerField()
|
cores = models.IntegerField()
|
||||||
ram_in_gb = models.FloatField()
|
ram_in_gb = models.FloatField()
|
||||||
|
vmid = models.IntegerField(null=True)
|
||||||
|
|
||||||
def recurring_price(self, recurring_period=RecurringPeriod.PER_MONTH):
|
def recurring_price(self, recurring_period=RecurringPeriod.PER_MONTH):
|
||||||
# TODO: move magic numbers in variables
|
# TODO: move magic numbers in variables
|
||||||
|
@ -97,7 +104,8 @@ class VMDiskImageProduct(models.Model):
|
||||||
|
|
||||||
size_in_gb = models.FloatField(null=True, blank=True)
|
size_in_gb = models.FloatField(null=True, blank=True)
|
||||||
import_url = models.URLField(null=True, blank=True)
|
import_url = models.URLField(null=True, blank=True)
|
||||||
|
image_source = models.CharField(max_length=128, null=True)
|
||||||
|
image_source_type = models.CharField(max_length=128, null=True)
|
||||||
storage_class = models.CharField(
|
storage_class = models.CharField(
|
||||||
max_length=32,
|
max_length=32,
|
||||||
choices=(
|
choices=(
|
||||||
|
@ -150,7 +158,7 @@ class VMDiskProduct(models.Model):
|
||||||
class VMNetworkCard(models.Model):
|
class VMNetworkCard(models.Model):
|
||||||
vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE)
|
vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE)
|
||||||
|
|
||||||
mac_address = models.IntegerField()
|
mac_address = models.BigIntegerField()
|
||||||
|
|
||||||
ip_address = models.GenericIPAddressField(blank=True,
|
ip_address = models.GenericIPAddressField(blank=True,
|
||||||
null=True)
|
null=True)
|
||||||
|
|
Loading…
Reference in a new issue