2016-05-24 06:19:49 +00:00
|
|
|
import os
|
2017-05-08 00:47:38 +00:00
|
|
|
import socket
|
2016-04-23 07:22:44 +00:00
|
|
|
|
2017-05-05 12:59:11 +00:00
|
|
|
import oca
|
2015-05-27 10:21:30 +00:00
|
|
|
from django.db import models
|
2017-04-26 05:10:52 +00:00
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2016-05-03 05:59:40 +00:00
|
|
|
from django.utils.functional import cached_property
|
2017-05-09 00:02:29 +00:00
|
|
|
from hosting.opennebula_functions import OpenNebulaManager
|
2017-04-24 22:25:05 +00:00
|
|
|
|
2017-05-07 23:57:44 +00:00
|
|
|
from django.conf import settings
|
|
|
|
|
2017-04-24 22:25:05 +00:00
|
|
|
from Crypto.PublicKey import RSA
|
2016-05-29 18:37:43 +00:00
|
|
|
from stored_messages.settings import stored_messages_settings
|
2016-05-24 06:19:49 +00:00
|
|
|
|
2017-05-04 04:19:32 +00:00
|
|
|
from membership.models import StripeCustomer, CustomUser
|
2017-04-24 22:07:50 +00:00
|
|
|
from utils.models import BillingAddress
|
2017-04-24 22:25:05 +00:00
|
|
|
from utils.mixins import AssignPermissionsMixin
|
2016-05-03 05:59:40 +00:00
|
|
|
from .managers import VMPlansManager
|
2017-05-08 00:47:38 +00:00
|
|
|
from oca.exceptions import OpenNebulaException
|
|
|
|
from oca.pool import WrongNameError
|
2016-05-03 05:59:40 +00:00
|
|
|
|
2017-05-08 10:53:03 +00:00
|
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
2016-04-18 00:52:19 +00:00
|
|
|
|
|
|
|
class VirtualMachineType(models.Model):
|
|
|
|
|
|
|
|
description = models.TextField()
|
|
|
|
base_price = models.FloatField()
|
|
|
|
memory_price = models.FloatField()
|
|
|
|
core_price = models.FloatField()
|
|
|
|
disk_size_price = models.FloatField()
|
2017-05-04 04:19:32 +00:00
|
|
|
cores = models.IntegerField()
|
|
|
|
memory = models.IntegerField()
|
|
|
|
disk_size = models.IntegerField()
|
2016-04-18 01:05:39 +00:00
|
|
|
|
|
|
|
def __str__(self):
|
2017-05-04 04:19:32 +00:00
|
|
|
return "VM Type %s" % (self.id)
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def final_price(self):
|
|
|
|
price = self.cores * self.core_price
|
|
|
|
price += self.memory * self.memory_price
|
|
|
|
price += self.disk_size * self.disk_size_price
|
|
|
|
return price
|
2016-04-18 00:52:19 +00:00
|
|
|
|
2016-04-19 06:04:15 +00:00
|
|
|
@classmethod
|
|
|
|
def get_serialized_vm_types(cls):
|
|
|
|
return [vm.get_serialized_data()
|
2016-04-20 06:03:32 +00:00
|
|
|
for vm in cls.objects.all()]
|
2016-04-23 07:22:44 +00:00
|
|
|
|
2017-05-04 04:19:32 +00:00
|
|
|
def calculate_price(self):
|
|
|
|
price = self.cores * self.core_price
|
|
|
|
price += self.memory * self.memory_price
|
|
|
|
price += self.disk_size * self.disk_size_price
|
|
|
|
# price += self.base_price
|
2016-04-23 07:22:44 +00:00
|
|
|
return price
|
2016-04-19 06:04:15 +00:00
|
|
|
|
2017-05-04 04:19:32 +00:00
|
|
|
# @classmethod
|
|
|
|
# def get_price(cls, vm_template):
|
|
|
|
# return cls.BASE_PRICE * vm_template
|
2017-04-29 17:39:55 +00:00
|
|
|
|
2017-05-04 04:19:32 +00:00
|
|
|
def get_specs(self):
|
2017-04-29 17:39:55 +00:00
|
|
|
return {
|
2017-05-04 04:19:32 +00:00
|
|
|
'memory': self.memory,
|
|
|
|
'cores': self.cores,
|
|
|
|
'disk_size': self.disk_size
|
2017-04-29 17:39:55 +00:00
|
|
|
}
|
|
|
|
|
2017-05-04 04:19:32 +00:00
|
|
|
# def calculate_price(self, vm_template):
|
|
|
|
# price = self.base_price * vm_template
|
|
|
|
# return price
|
2016-04-19 06:04:15 +00:00
|
|
|
|
2017-05-04 04:19:32 +00:00
|
|
|
# def defeault_price(self):
|
|
|
|
# price = self.base_price
|
|
|
|
# price += self.core_price
|
|
|
|
# price += self.memory_price
|
|
|
|
# price += self.disk_size_price * 10
|
|
|
|
# return price
|
2016-04-20 06:03:32 +00:00
|
|
|
|
2016-04-18 00:52:19 +00:00
|
|
|
def get_serialized_data(self):
|
2016-04-19 06:04:15 +00:00
|
|
|
return {
|
|
|
|
'description': self.description,
|
|
|
|
'core_price': self.core_price,
|
|
|
|
'disk_size_price': self.disk_size_price,
|
|
|
|
'memory_price': self.memory_price,
|
|
|
|
'id': self.id,
|
2017-05-04 04:19:32 +00:00
|
|
|
'final_price': self.final_price,
|
|
|
|
'cores': self.cores,
|
|
|
|
'memory': self.memory,
|
|
|
|
'disk_size': self.disk_size
|
|
|
|
|
2016-04-19 06:04:15 +00:00
|
|
|
}
|
2016-04-23 07:22:44 +00:00
|
|
|
|
|
|
|
|
2016-06-26 19:50:48 +00:00
|
|
|
class VirtualMachinePlan(AssignPermissionsMixin, models.Model):
|
2016-05-27 05:51:10 +00:00
|
|
|
|
|
|
|
PENDING_STATUS = 'pending'
|
|
|
|
ONLINE_STATUS = 'online'
|
|
|
|
CANCELED_STATUS = 'canceled'
|
|
|
|
|
|
|
|
VM_STATUS_CHOICES = (
|
|
|
|
(PENDING_STATUS, 'Pending for activation'),
|
|
|
|
(ONLINE_STATUS, 'Online'),
|
|
|
|
(CANCELED_STATUS, 'Canceled')
|
|
|
|
)
|
|
|
|
|
2017-05-04 04:19:32 +00:00
|
|
|
# DJANGO = 'django'
|
|
|
|
# RAILS = 'rails'
|
|
|
|
# NODEJS = 'nodejs'
|
|
|
|
|
|
|
|
# VM_CONFIGURATION = (
|
|
|
|
# (DJANGO, 'Ubuntu 14.04, Django'),
|
|
|
|
# (RAILS, 'Ubuntu 14.04, Rails'),
|
|
|
|
# (NODEJS, 'Debian, NodeJS'),
|
|
|
|
# )
|
2016-06-07 05:29:22 +00:00
|
|
|
|
|
|
|
VM_CONFIGURATION = (
|
2017-05-04 04:19:32 +00:00
|
|
|
('debian', 'Debian 8'),
|
|
|
|
('ubuntu', 'Ubuntu 16.06'),
|
|
|
|
('devuan', 'Devuan 1'),
|
|
|
|
('centos', 'CentOS 7')
|
2016-06-07 05:29:22 +00:00
|
|
|
)
|
|
|
|
|
2016-07-11 03:08:51 +00:00
|
|
|
permissions = ('view_virtualmachineplan',
|
|
|
|
'cancel_virtualmachineplan',
|
|
|
|
'change_virtualmachineplan')
|
2016-06-26 19:50:48 +00:00
|
|
|
|
2016-04-23 07:22:44 +00:00
|
|
|
cores = models.IntegerField()
|
|
|
|
memory = models.IntegerField()
|
|
|
|
disk_size = models.IntegerField()
|
2017-05-04 04:19:32 +00:00
|
|
|
vm_type = models.ForeignKey(VirtualMachineType, null=True)
|
2016-04-23 07:22:44 +00:00
|
|
|
price = models.FloatField()
|
2016-06-07 05:29:22 +00:00
|
|
|
public_key = models.TextField(blank=True)
|
2016-05-27 05:51:10 +00:00
|
|
|
status = models.CharField(max_length=20, choices=VM_STATUS_CHOICES, default=PENDING_STATUS)
|
2016-06-07 05:29:22 +00:00
|
|
|
ip = models.CharField(max_length=50, blank=True)
|
|
|
|
configuration = models.CharField(max_length=20, choices=VM_CONFIGURATION)
|
2017-05-06 23:09:01 +00:00
|
|
|
opennebula_id = models.IntegerField(null=True)
|
2016-04-23 07:22:44 +00:00
|
|
|
|
2016-05-03 05:59:40 +00:00
|
|
|
objects = VMPlansManager()
|
|
|
|
|
2016-06-26 19:50:48 +00:00
|
|
|
class Meta:
|
|
|
|
permissions = (
|
|
|
|
('view_virtualmachineplan', 'View Virtual Machine Plan'),
|
|
|
|
('cancel_virtualmachineplan', 'Cancel Virtual Machine Plan'),
|
|
|
|
)
|
|
|
|
|
2016-05-14 06:42:42 +00:00
|
|
|
def __str__(self):
|
2016-06-03 05:07:47 +00:00
|
|
|
return self.name
|
2016-05-14 06:42:42 +00:00
|
|
|
|
2017-05-04 04:19:32 +00:00
|
|
|
# @cached_property
|
|
|
|
# def hosting_company_name(self):
|
|
|
|
# return self.vm_type.get_hosting_company_display()
|
2016-05-03 05:59:40 +00:00
|
|
|
|
2017-05-04 04:19:32 +00:00
|
|
|
# @cached_property
|
|
|
|
# def location(self):
|
|
|
|
# return self.vm_type.get_location_display()
|
2016-05-27 05:51:10 +00:00
|
|
|
|
2016-05-04 05:16:41 +00:00
|
|
|
@cached_property
|
|
|
|
def name(self):
|
|
|
|
name = 'vm-%s' % self.id
|
|
|
|
return name
|
|
|
|
|
2016-05-29 18:37:43 +00:00
|
|
|
@cached_property
|
|
|
|
def notifications(self):
|
|
|
|
stripe_customer = StripeCustomer.objects.get(hostingorder__vm_plan=self)
|
|
|
|
backend = stored_messages_settings.STORAGE_BACKEND()
|
|
|
|
messages = backend.inbox_list(stripe_customer.user)
|
|
|
|
return messages
|
|
|
|
|
2016-04-23 07:22:44 +00:00
|
|
|
@classmethod
|
|
|
|
def create(cls, data, user):
|
|
|
|
instance = cls.objects.create(**data)
|
2016-06-26 19:50:48 +00:00
|
|
|
instance.assign_permissions(user)
|
2016-04-26 06:16:03 +00:00
|
|
|
return instance
|
|
|
|
|
2016-06-10 04:50:49 +00:00
|
|
|
def cancel_plan(self):
|
|
|
|
self.status = self.CANCELED_STATUS
|
|
|
|
self.save(update_fields=['status'])
|
|
|
|
|
2017-05-09 00:02:29 +00:00
|
|
|
@classmethod
|
|
|
|
def get_vm(self, email, vm_id):
|
|
|
|
# Get opennebula client
|
|
|
|
opennebula_client = OpenNebulaManager()
|
|
|
|
|
|
|
|
# Get vm given the id
|
|
|
|
vm = opennebula_client.get_vm(
|
|
|
|
email,
|
|
|
|
vm_id
|
|
|
|
)
|
|
|
|
|
|
|
|
# Parse vm data
|
|
|
|
vm_data = OpenNebulaManager.parse_vm(vm)
|
|
|
|
|
|
|
|
return vm_data
|
|
|
|
|
2017-05-07 23:57:44 +00:00
|
|
|
@classmethod
|
|
|
|
def get_vms(self, email):
|
2017-05-09 00:02:29 +00:00
|
|
|
|
|
|
|
# Get opennebula client
|
|
|
|
opennebula_client = OpenNebulaManager()
|
|
|
|
|
|
|
|
# Get vm pool
|
|
|
|
vm_pool = opennebula_client.get_vms(email)
|
2017-05-07 23:57:44 +00:00
|
|
|
|
|
|
|
# Reset total price
|
|
|
|
self.total_price = 0
|
|
|
|
vms = []
|
|
|
|
# Add vm in vm_pool to context
|
|
|
|
for vm in vm_pool:
|
2017-05-09 00:02:29 +00:00
|
|
|
vm_data = OpenNebulaManager.parse_vm(vm)
|
|
|
|
vms.append(vm_data)
|
2017-05-07 23:57:44 +00:00
|
|
|
# self.total_price += price
|
|
|
|
# self.save()
|
|
|
|
return vms
|
|
|
|
|
2016-04-26 06:16:03 +00:00
|
|
|
|
2016-07-11 03:08:51 +00:00
|
|
|
class HostingOrder(AssignPermissionsMixin, models.Model):
|
2016-05-03 05:59:40 +00:00
|
|
|
|
|
|
|
ORDER_APPROVED_STATUS = 'Approved'
|
|
|
|
ORDER_DECLINED_STATUS = 'Declined'
|
|
|
|
|
2016-05-12 06:57:34 +00:00
|
|
|
vm_plan = models.ForeignKey(VirtualMachinePlan, related_name='hosting_orders')
|
2016-04-26 06:16:03 +00:00
|
|
|
customer = models.ForeignKey(StripeCustomer)
|
|
|
|
billing_address = models.ForeignKey(BillingAddress)
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
approved = models.BooleanField(default=False)
|
2016-05-03 05:59:40 +00:00
|
|
|
last4 = models.CharField(max_length=4)
|
|
|
|
cc_brand = models.CharField(max_length=10)
|
2016-04-26 06:16:03 +00:00
|
|
|
stripe_charge_id = models.CharField(max_length=100, null=True)
|
|
|
|
|
2016-07-11 03:08:51 +00:00
|
|
|
permissions = ('view_hostingorder',)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
permissions = (
|
|
|
|
('view_hostingorder', 'View Hosting Order'),
|
|
|
|
)
|
|
|
|
|
2016-05-14 06:42:42 +00:00
|
|
|
def __str__(self):
|
|
|
|
return "%s" % (self.id)
|
|
|
|
|
2016-05-03 05:59:40 +00:00
|
|
|
@cached_property
|
|
|
|
def status(self):
|
|
|
|
return self.ORDER_APPROVED_STATUS if self.approved else self.ORDER_DECLINED_STATUS
|
|
|
|
|
2016-04-26 06:16:03 +00:00
|
|
|
@classmethod
|
2016-05-12 06:57:34 +00:00
|
|
|
def create(cls, vm_plan=None, customer=None, billing_address=None):
|
|
|
|
instance = cls.objects.create(vm_plan=vm_plan, customer=customer,
|
2016-04-26 06:16:03 +00:00
|
|
|
billing_address=billing_address)
|
2016-07-11 03:08:51 +00:00
|
|
|
instance.assign_permissions(customer.user)
|
2016-04-26 06:16:03 +00:00
|
|
|
return instance
|
|
|
|
|
|
|
|
def set_approved(self):
|
|
|
|
self.approved = True
|
|
|
|
self.save()
|
|
|
|
|
|
|
|
def set_stripe_charge(self, stripe_charge):
|
|
|
|
self.stripe_charge_id = stripe_charge.id
|
2016-05-03 05:59:40 +00:00
|
|
|
self.last4 = stripe_charge.source.last4
|
|
|
|
self.cc_brand = stripe_charge.source.brand
|
2016-04-26 06:16:03 +00:00
|
|
|
self.save()
|
|
|
|
|
2016-04-23 07:22:44 +00:00
|
|
|
|
2017-05-04 04:19:32 +00:00
|
|
|
class UserHostingKey(models.Model):
|
|
|
|
user = models.ForeignKey(CustomUser)
|
|
|
|
public_key = models.TextField()
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
name = models.CharField(max_length=100)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def generate_RSA(bits=2048):
|
|
|
|
'''
|
|
|
|
Generate an RSA keypair with an exponent of 65537 in PEM format
|
|
|
|
param: bits The key length in bits
|
|
|
|
Return private key and public key
|
|
|
|
'''
|
|
|
|
new_key = RSA.generate(2048, os.urandom)
|
|
|
|
public_key = new_key.publickey().exportKey("OpenSSH")
|
|
|
|
private_key = new_key.exportKey("PEM")
|
|
|
|
return private_key, public_key
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def generate_keys(cls):
|
|
|
|
private_key, public_key = cls.generate_RSA()
|
|
|
|
# self.public_key = public_key
|
|
|
|
# self.save(update_fields=['public_key'])
|
|
|
|
return private_key, public_key
|
|
|
|
|
|
|
|
|
2017-04-23 16:54:41 +00:00
|
|
|
class ManageVM(models.Model):
|
2017-03-15 12:02:55 +00:00
|
|
|
def has_add_permission(self, request):
|
|
|
|
return False
|
2016-04-23 07:22:44 +00:00
|
|
|
|
2017-03-15 12:02:55 +00:00
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
|
|
return False
|
2016-04-23 07:22:44 +00:00
|
|
|
|
2017-03-15 12:02:55 +00:00
|
|
|
class Meta:
|
|
|
|
managed = False
|
2017-05-05 12:59:11 +00:00
|
|
|
|
|
|
|
class HostingBill(AssignPermissionsMixin, models.Model):
|
|
|
|
customer = models.ForeignKey(StripeCustomer)
|
|
|
|
billing_address = models.ForeignKey(BillingAddress)
|
2017-05-06 12:44:08 +00:00
|
|
|
total_price = models.FloatField(default=0.0)
|
2017-05-05 12:59:11 +00:00
|
|
|
|
|
|
|
permissions = ('view_hostingbill',)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
permissions = (
|
|
|
|
('view_hostingbill', 'View Hosting Bill'),
|
|
|
|
)
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return "%s" % (self.customer.user.email)
|
|
|
|
|
2017-05-07 04:43:28 +00:00
|
|
|
def get_vms(self):
|
|
|
|
# Get User
|
|
|
|
user_email = self.customer.user.email
|
|
|
|
|
|
|
|
# Connect to open nebula server
|
2017-05-08 00:47:38 +00:00
|
|
|
try:
|
|
|
|
client = oca.Client("{0}:{1}".format(settings.OPENNEBULA_USERNAME,
|
|
|
|
settings.OPENNEBULA_PASSWORD),
|
|
|
|
"{protocol}://{domain}:{port}{endpoint}".format(
|
|
|
|
protocol=settings.OPENNEBULA_PROTOCOL,
|
|
|
|
domain=settings.OPENNEBULA_DOMAIN,
|
|
|
|
port=settings.OPENNEBULA_PORT,
|
|
|
|
endpoint=settings.OPENNEBULA_ENDPOINT
|
|
|
|
))
|
|
|
|
except OpenNebulaException as err:
|
|
|
|
logger.error("Error : {0}".format(err))
|
|
|
|
return None
|
|
|
|
except socket.timeout:
|
|
|
|
logger.error("Socket timeout error.")
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
2017-05-07 04:43:28 +00:00
|
|
|
# Get open nebula user id for given email
|
|
|
|
user_pool = oca.UserPool(client)
|
|
|
|
user_pool.info()
|
2017-05-08 00:47:38 +00:00
|
|
|
try:
|
|
|
|
user_id = user_pool.get_by_name(user_email).id
|
|
|
|
except WrongNameError as wrong_name_err:
|
|
|
|
logger.error("User {0} does not exist.", user_email)
|
|
|
|
return None
|
2017-05-07 04:43:28 +00:00
|
|
|
# Get vm_pool for given user_id
|
|
|
|
vm_pool = oca.VirtualMachinePool(client)
|
|
|
|
vm_pool.info(filter=user_id)
|
|
|
|
|
|
|
|
# Reset total price
|
|
|
|
self.total_price = 0
|
2017-05-07 14:09:41 +00:00
|
|
|
vms = []
|
2017-05-07 04:43:28 +00:00
|
|
|
# Add vm in vm_pool to context
|
|
|
|
for vm in vm_pool:
|
|
|
|
name = vm.name
|
|
|
|
cores = int(vm.template.vcpu)
|
|
|
|
memory = int(vm.template.memory) / 1024
|
|
|
|
# Check if vm has more than one disk
|
|
|
|
if 'DISK' in vm.template.multiple:
|
|
|
|
disk_size = 0
|
|
|
|
for disk in vm.template.disks:
|
|
|
|
disk_size += int(disk.size) / 1024
|
|
|
|
else:
|
|
|
|
disk_size = int(vm.template.disk.size) / 1024
|
|
|
|
|
|
|
|
#TODO: Replace with vm plan
|
|
|
|
price = 0.6 * disk_size + 2 * memory + 5 * cores
|
|
|
|
vm = {}
|
|
|
|
vm['name'] = name
|
|
|
|
vm['price'] = price
|
|
|
|
vm['disk_size'] = disk_size
|
|
|
|
vm['cores'] = cores
|
|
|
|
vm['memory'] = memory
|
|
|
|
vms.append(vm)
|
|
|
|
self.total_price += price
|
|
|
|
|
|
|
|
self.save()
|
2017-05-07 14:09:41 +00:00
|
|
|
return vms
|
2017-05-07 04:43:28 +00:00
|
|
|
|
|
|
|
|
2017-05-08 10:53:03 +00:00
|
|
|
|
|
|
|
def get_user_opennebula_password():
|
|
|
|
'''
|
|
|
|
TODO: Implement the way we obtain the user's opennebula password
|
|
|
|
'''
|
|
|
|
pw = os.environ.get('OPENNEBULA_USER_PW')
|
|
|
|
if pw is None:
|
|
|
|
raise Exception("Define OPENNEBULA_USER_PW env variable")
|
|
|
|
return pw
|