Commit 23646e98 by Dudás Ádám

vm: moving code from one to vm, creating new model elements

parent 6ad839e2
...@@ -67,11 +67,12 @@ class Rule(models.Model): ...@@ -67,11 +67,12 @@ class Rule(models.Model):
help_text=_("The type of entity the rule " help_text=_("The type of entity the rule "
"belongs to.")) "belongs to."))
nat = models.BooleanField(default=False, verbose_name=_("NAT"), nat = models.BooleanField(default=False, verbose_name=_("NAT"),
help_text=_("If network address translation " help_text=_("Indicates that network address "
"shoud be done.")) "translation should be done."))
nat_dport = models.IntegerField(blank=True, null=True, nat_dport = models.IntegerField(blank=True, null=True,
help_text=_( help_text=_("Rewrite destination port "
"Rewrite destination port number to."), "number to this if NAT is "
"needed."),
validators=[MinValueValidator(1), validators=[MinValueValidator(1),
MaxValueValidator(65535)]) MaxValueValidator(65535)])
created_at = models.DateTimeField( created_at = models.DateTimeField(
...@@ -373,6 +374,14 @@ class Host(models.Model): ...@@ -373,6 +374,14 @@ class Host(models.Model):
def __unicode__(self): def __unicode__(self):
return self.hostname return self.hostname
@property
def incoming_rules(self):
return self.rules.filter(direction='1')
@property
def outgoing_rules(self):
return self.rules.filter(direction='0')
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
id = self.id id = self.id
if not self.id and self.ipv6 == "auto": if not self.id and self.ipv6 == "auto":
...@@ -549,6 +558,30 @@ class Host(models.Model): ...@@ -549,6 +558,30 @@ class Host(models.Model):
""" """
return self.hostname + u'.' + unicode(self.vlan.domain) return self.hostname + u'.' + unicode(self.vlan.domain)
def get_public_endpoints(self, port, protocol='tcp'):
"""Get public IPv4 and IPv6 endpoints for local port.
Optionally the required protocol (e.g. TCP, UDP) can be specified.
"""
# IPv4
public_ipv4 = self.pub_ipv4 if self.pub_ipv4 else self.ipv4
# try get matching port(s) without NAT
ports = self.incoming_rules.filter(accept=True, dport=port,
nat=False, proto=protocol)
if ports.exists():
public_port = ports[0].dport
else:
# try get matching port(s) with NAT
ports = self.incoming_rules.filter(accept=True, nat_dport=port,
nat=True, proto=protocol)
public_port = ports[0].dport if ports.exists() else None
endpoints['ipv4'] = ((public_ipv4, public_port) if public_port else
None)
# IPv6
blocked = self.incoming_rules.filter(accept=False, dport=port,
proto=protocol).exists()
endpoints['ipv6'] = (self.ipv6, port) if not blocked else None
return endpoints
class Firewall(models.Model): class Firewall(models.Model):
name = models.CharField(max_length=20, unique=True) name = models.CharField(max_length=20, unique=True)
......
...@@ -17,6 +17,7 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -17,6 +17,7 @@ from django.utils.translation import ugettext_lazy as _
from firewall.models import Host, Vlan from firewall.models import Host, Vlan
from store.api import StoreApi from store.api import StoreApi
from vm.models import Template
from .util import keygen from .util import keygen
import django.conf import django.conf
...@@ -183,8 +184,6 @@ class SshKey(models.Model): ...@@ -183,8 +184,6 @@ class SshKey(models.Model):
return u"%s (%s)" % (keycomment, self.user) return u"%s (%s)" % (keycomment, self.user)
TEMPLATE_STATES = (("INIT", _('init')), ("PREP", _('perparing')),
("SAVE", _('saving')), ("READY", _('ready')))
TYPES = {"LAB": {"verbose_name": _('lab'), "id": "LAB", TYPES = {"LAB": {"verbose_name": _('lab'), "id": "LAB",
"suspend": td(hours=5), "delete": td(days=15), "suspend": td(hours=5), "delete": td(days=15),
"help_text": _('For lab or homework with short lifetime.')}, "help_text": _('For lab or homework with short lifetime.')},
...@@ -264,485 +263,3 @@ class Share(models.Model): ...@@ -264,485 +263,3 @@ class Share(models.Model):
def get_used_quota(self): def get_used_quota(self):
return self.template.get_credits_per_instance() * self.instance_limit return self.template.get_credits_per_instance() * self.instance_limit
class Disk(models.Model):
"""Virtual disks automatically synchronized with OpenNebula."""
name = models.CharField(max_length=100, unique=True,
verbose_name=_('name'))
class Meta:
ordering = ['name']
verbose_name = _('disk')
verbose_name_plural = _('disks')
def __unicode__(self):
return u"%s (#%d)" % (self.name, self.id)
@staticmethod
def update(delete=True):
"""Get and register virtual disks from OpenNebula."""
try:
from .tasks import UpdateDiskTask
x = UpdateDiskTask.delay().get(timeout=10)
x[0]
except:
return
with transaction.commit_on_success():
l = []
for d in x:
id = int(d['id'])
name = d['name']
try:
d, created = Disk.objects.get_or_create(id=id)
d.name = name
d.save()
except:
pass
l.append(id)
if delete:
Disk.objects.exclude(id__in=l).delete()
class Network(models.Model):
"""Virtual networks automatically synchronized with OpenNebula."""
name = models.CharField(max_length=100, unique=True,
verbose_name=_('name'))
nat = models.BooleanField(verbose_name=_('NAT'),
help_text=_('If network address translation is '
'done.'))
public = models.BooleanField(verbose_name=_('public'),
help_text=_('If internet gateway is '
'available.'))
class Meta:
ordering = ['name']
verbose_name = _('network')
verbose_name_plural = _('networks')
def __unicode__(self):
return self.name
@staticmethod
def update():
"""Get and register virtual networks from OpenNebula."""
try:
from .tasks import UpdateNetworkTask
x = UpdateNetworkTask.delay().get(timeout=10)
x[0]
except:
return
with transaction.commit_on_success():
l = []
for n in x:
id = int(n['id'])
name = n['name']
try:
n, created = Network.objects.get_or_create(id=id)
n.name = name
n.save()
except:
pass
l.append(id)
Network.objects.exclude(id__in=l).delete()
def get_vlan(self):
return Vlan.objects.get(vid=self.id)
class InstanceType(models.Model):
"""Instance types in OCCI configuration (manually synchronized)."""
name = models.CharField(max_length=100, unique=True,
verbose_name=_('name'))
CPU = models.IntegerField(help_text=_('CPU cores.'))
RAM = models.IntegerField(help_text=_('Mebibytes of memory.'))
credit = models.IntegerField(verbose_name=_('credits'),
help_text=_('Price of instance.'))
class Meta:
ordering = ['credit']
verbose_name = _('instance type')
verbose_name_plural = _('instance types')
def __unicode__(self):
return u"%s" % self.name
TEMPLATE_STATES = (('NEW', _('new')), ('SAVING', _('saving')),
('READY', _('ready')), )
class Template(models.Model):
"""Virtual machine template specifying OS, disk, type and network."""
name = models.CharField(max_length=100, unique=True,
verbose_name=_('name'))
access_type = models.CharField(max_length=10,
choices=[('rdp', 'rdp'), (
'nx', 'nx'), ('ssh', 'ssh')],
verbose_name=_('access method'))
disk = models.ForeignKey(Disk, verbose_name=_(
'disk'), related_name='template_set')
instance_type = models.ForeignKey(
InstanceType, related_name='template_set',
verbose_name=_('instance type'))
network = models.ForeignKey(Network, verbose_name=_('network'),
related_name='template_set')
owner = models.ForeignKey(User, verbose_name=_('owner'),
related_name='template_set')
created_at = models.DateTimeField(auto_now_add=True,
verbose_name=_('created at'))
state = models.CharField(max_length=10, choices=TEMPLATE_STATES,
default='NEW')
public = models.BooleanField(verbose_name=_('public'), default=False,
help_text=_('If other users can derive '
'templates of this one.'))
description = models.TextField(verbose_name=_('description'), blank=True)
system = models.TextField(verbose_name=_('operating system'), blank=True,
help_text=(_('Name of operating system in '
'format like "%s".') %
"Ubuntu 12.04 LTS Desktop amd64"))
class Meta:
verbose_name = _('template')
verbose_name_plural = _('templates')
ordering = ['name', ]
def __unicode__(self):
return self.name
def running_instances(self):
return self.instance_set.exclude(state='DONE').count()
@property
def os_type(self):
if self.access_type == 'rdp':
return "win"
else:
return "linux"
@transaction.autocommit
def safe_delete(self):
if not self.instance_set.exclude(state='DONE').exists():
self.delete()
return True
else:
logger.info("Could not delete template. Instances still running!")
return False
def get_credits_per_instance(self):
return self.instance_type.credit
def get_shares_for(self, user=None):
shares = Share.objects.all().filter(template=self)
if user:
return shares.filter(owner=user)
else:
return shares
def get_share_quota_usage_for(self, user=None, type=None):
if type is None:
c = self.get_credits_per_instance()
else:
c = type.credit
shares = self.get_shares_for(user)
usage = 0
for share in shares:
usage += share.instance_limit * c
return usage
class Instance(models.Model):
"""Virtual machine instance."""
name = models.CharField(max_length=100,
verbose_name=_('name'), blank=True)
ip = models.IPAddressField(blank=True, null=True,
verbose_name=_('IP address'))
template = models.ForeignKey(Template, verbose_name=_('template'),
related_name='instance_set')
owner = models.ForeignKey(User, verbose_name=_('owner'),
related_name='instance_set')
created_at = models.DateTimeField(auto_now_add=True,
verbose_name=_('created at'))
state = models.CharField(max_length=20,
choices=[('DEPLOYABLE', _('deployable')),
('PENDING', _('pending')),
('DONE', _('done')),
('ACTIVE', _('active')),
('UNKNOWN', _('unknown')),
('STOPPED', _('suspended')),
('FAILED', _('failed'))],
default='DEPLOYABLE')
active_since = models.DateTimeField(null=True, blank=True,
verbose_name=_('active since'),
help_text=_('Time stamp of successful '
'boot report.'))
firewall_host = models.ForeignKey(Host, blank=True, null=True,
verbose_name=_('host in firewall'),
related_name='instance_set')
pw = models.CharField(max_length=20, verbose_name=_('password'),
help_text=_('Original password of instance'))
one_id = models.IntegerField(unique=True, blank=True, null=True,
verbose_name=_('OpenNebula ID'))
share = models.ForeignKey('Share', blank=True, null=True,
verbose_name=_('share'),
related_name='instance_set')
time_of_suspend = models.DateTimeField(default=None,
verbose_name=_('time of suspend'),
null=True, blank=True)
time_of_delete = models.DateTimeField(default=None,
verbose_name=_('time of delete'),
null=True, blank=True)
waiting = models.BooleanField(default=False)
class Meta:
verbose_name = _('instance')
verbose_name_plural = _('instances')
ordering = ['pk', ]
def __unicode__(self):
return self.name
@models.permalink
def get_absolute_url(self):
return ('one.views.vm_show', None, {'iid': self.id})
def get_port(self, use_ipv6=False):
"""Get public port number for default access method."""
proto = self.template.access_type
if self.nat and not use_ipv6:
return ({"rdp": 23000, "nx": 22000, "ssh": 22000}[proto] +
int(self.ip.split('.')[2]) * 256 +
int(self.ip.split('.')[3]))
else:
return {"rdp": 3389, "nx": 22, "ssh": 22}[proto]
def get_connect_host(self, use_ipv6=False):
"""Get public hostname."""
if self.firewall_host is None:
return _('None')
proto = 'ipv6' if use_ipv6 else 'ipv4'
return self.firewall_host.get_hostname(proto=proto)
def get_connect_uri(self, use_ipv6=False):
"""Get access parameters in URI format."""
try:
proto = self.template.access_type
if proto == 'ssh':
proto = 'sshterm'
port = self.get_port(use_ipv6=use_ipv6)
host = self.get_connect_host(use_ipv6=use_ipv6)
pw = self.pw
return ("%(proto)s:cloud:%(pw)s:%(host)s:%(port)d" %
{"port": port, "proto": proto, "pw": pw,
"host": host})
except:
return
@property
def nat(self):
if self.firewall_host is not None:
return self.firewall_host.shared_ip
elif self.template is not None:
return self.template.network.nat
else:
return False
def get_age(self):
"""Get age of VM in seconds."""
from datetime import datetime
age = 0
try:
age = (datetime.now().replace(tzinfo=None)
- self.active_since.replace(tzinfo=None)).seconds
except:
pass
return age
@staticmethod
def _create_context(pw, hostname, smb_password, ssh_private_key, owner,
token, extra):
"""Return XML context configuration with given parameters."""
ctx = u'''
<SOURCE>web</SOURCE>
<HOSTNAME>%(hostname)s</HOSTNAME>
<NEPTUN>%(neptun)s</NEPTUN>
<USERPW>%(pw)s</USERPW>
<SMBPW>%(smbpw)s</SMBPW>
<SSHPRIV>%(sshkey)s</SSHPRIV>
<BOOTURL>%(booturl)s</BOOTURL>
<SERVER>store.cloud.ik.bme.hu</SERVER>
%(extra)s
''' % {
"pw": escape(pw),
"hostname": escape(hostname),
"smbpw": escape(smb_password),
"sshkey": escape(ssh_private_key),
"neptun": escape(owner),
"booturl": "%sb/%s/" % (CLOUD_URL, token),
"extra": extra
}
return ctx
def _create_host(self, hostname, occi_result):
"""Create firewall host for recently submitted Instance."""
host = Host(
vlan=Vlan.objects.get(name=self.template.network.name),
owner=self.owner, hostname=hostname,
mac=occi_result['interfaces'][0]['mac'],
ipv4=occi_result['interfaces'][0]['ip'], ipv6='auto',
)
if self.template.network.nat:
host.pub_ipv4 = Vlan.objects.get(
name=self.template.network.name).snat_ip
host.shared_ip = True
try:
host.save()
except:
for i in Host.objects.filter(ipv4=host.ipv4).all():
logger.warning('Delete orphan fw host (%s) of %s.' % (i, self))
i.delete()
for i in Host.objects.filter(mac=host.mac).all():
logger.warning('Delete orphan fw host (%s) of %s.' % (i, self))
i.delete()
host.save()
host.enable_net()
port = {"rdp": 3389, "nx": 22, "ssh": 22}[self.template.access_type]
host.add_port("tcp", self.get_port(), port)
self.firewall_host = host
self.save()
@classmethod
def submit(cls, template, owner, extra="", share=None):
"""Submit a new instance to OpenNebula."""
inst = Instance(pw=pwgen(), template=template, owner=owner,
share=share, state='PENDING', waiting=True)
inst.save()
hostname = u"%d" % (inst.id, )
token = signing.dumps(inst.id, salt='activate')
try:
details = owner.cloud_details
except:
details = UserCloudDetails(user=owner)
details.save()
ctx = cls._create_context(inst.pw, hostname, details.smb_password,
details.ssh_private_key, owner.username,
token, extra)
try:
from .tasks import CreateInstanceTask
x = CreateInstanceTask.delay(
name=u"%s %d" % (owner.username, inst.id),
instance_type=template.instance_type.name,
disk_id=int(template.disk.id),
network_id=int(template.network.id),
ctx=ctx,
)
res = x.get(timeout=10)
res['one_id']
except:
inst.delete()
raise Exception("Unable to create VM instance.")
inst.one_id = res['one_id']
inst.ip = res['interfaces'][0]['ip']
inst.name = ("%(neptun)s %(template)s (%(id)d)" %
{'neptun': owner.username, 'template': template.name,
'id': inst.one_id})
inst.save()
inst._create_host(hostname, res)
return inst
def one_delete(self):
"""Delete host in OpenNebula."""
if self.template.state != "DONE":
self.check_if_is_save_as_done()
if self.one_id and self.state != 'DONE':
self.waiting = True
self.save()
from .tasks import DeleteInstanceTask
DeleteInstanceTask.delay(one_id=self.one_id)
self.firewall_host_delete()
def firewall_host_delete(self):
if self.firewall_host:
h = self.firewall_host
self.firewall_host = None
try:
self.save()
except:
pass
h.delete()
def _change_state(self, new_state):
"""Change host state in OpenNebula."""
from .tasks import ChangeInstanceStateTask
ChangeInstanceStateTask.delay(one_id=self.one_id, new_state=new_state)
self.waiting = True
self.save()
def stop(self):
self._change_state("STOPPED")
def resume(self):
self._change_state("RESUME")
def poweroff(self):
self._change_state("POWEROFF")
def restart(self):
self._change_state("RESET")
self.waiting = False
self.save()
def renew(self, which='both'):
if which in ['suspend', 'both']:
self.time_of_suspend = self.share_type['suspendx']
if which in ['delete', 'both']:
self.time_of_delete = self.share_type['deletex']
if not (which in ['suspend', 'delete', 'both']):
raise ValueError('No such expiration type.')
self.save()
@property
def share_type(self):
if self.share:
return self.share.get_type()
else:
return Share.extend_type(DEFAULT_TYPE)
def save_as(self):
"""Save image and shut down."""
imgname = "template-%d-%d" % (self.template.id, self.id)
from .tasks import SaveAsTask
SaveAsTask.delay(one_id=self.one_id, new_img=imgname)
self._change_state("SHUTDOWN")
self.save()
t = self.template
t.state = 'SAVING'
t.save()
def check_if_is_save_as_done(self):
if self.state != 'DONE':
return False
Disk.update(delete=False)
imgname = "template-%d-%d" % (self.template.id, self.id)
disks = Disk.objects.filter(name=imgname)
if len(disks) != 1:
return False
self.template.disk_id = disks[0].id
self.template.state = 'READY'
self.template.save()
self.firewall_host_delete()
return True
def delete_instance_pre(sender, instance, using, **kwargs):
if instance.state != 'DONE':
instance.one_delete()
pre_delete.connect(delete_instance_pre, sender=Instance,
dispatch_uid="delete_instance_pre")
...@@ -6,6 +6,7 @@ import json ...@@ -6,6 +6,7 @@ import json
from django.db import models, transaction from django.db import models, transaction
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models.signals import post_save, post_delete from django.db.models.signals import post_save, post_delete
from model_utils.models import TimeStampedModel
from .tasks import StorageDriver from .tasks import StorageDriver
...@@ -27,20 +28,18 @@ class DataStore(models.Model): ...@@ -27,20 +28,18 @@ class DataStore(models.Model):
return u'%s (%s)' % (self.name, self.path) return u'%s (%s)' % (self.name, self.path)
class Disk(models.Model): class Disk(models.Model, TimeStampedModel):
"""Virtual disks automatically synchronized with OpenNebula.""" """Virtual disks."""
FORMATS = [('qcow2', 'qcow2'), ('raw', 'raw'), ('iso', 'iso')]
TYPES = [('snapshot', 'snapshot'), ('normal', 'normal')]
name = models.CharField(max_length=100, unique=True, name = models.CharField(max_length=100, unique=True,
verbose_name=_('name')) verbose_name=_('name'))
datastore = models.ForeignKey('DataStore') datastore = models.ForeignKey('DataStore')
format = models.CharField(max_length=10, format = models.CharField(max_length=10, choices=FORMAT)
choices=(('qcow2', 'qcow2'), ('raw', 'raw')))
size = models.IntegerField() size = models.IntegerField()
type = models.CharField(max_length=10, type = models.CharField(max_length=10, choices=TYPES)
choices=(('snapshot', 'snapshot'),
('normal', 'normal')))
base = models.ForeignKey('Disk', related_name='snapshots', base = models.ForeignKey('Disk', related_name='snapshots',
null=True, blank=True) null=True, blank=True)
original_parent = models.ForeignKey('Disk', null=True, blank=True)
created = models.BooleanField(default=False) created = models.BooleanField(default=False)
class Meta: class Meta:
...@@ -97,7 +96,7 @@ class Disk(models.Model): ...@@ -97,7 +96,7 @@ class Disk(models.Model):
@classmethod @classmethod
def update_disks(cls, delete=True): def update_disks(cls, delete=True):
"""Get and register virtual disks from OpenNebula.""" """Get and register virtual disks from storage driver."""
try: try:
json_data = StorageDriver.list_disks.delay().get(timeout=10) json_data = StorageDriver.list_disks.delay().get(timeout=10)
disks = json.loads(json_data) disks = json.loads(json_data)
......
from django.db import models
from model_utils.models import TimeStampedModel
from firewall.models import Vlan
from storage.models import Disk, Image
class BaseResourceConfigModel():
"""Abstract base class for models with base resource configuration
parameters.
"""
CPU = models.IntegerField(help_text=_('CPU cores.'))
RAM = models.IntegerField(help_text=_('Mebibytes of memory.'))
max_RAM = models.IntegerField(help_text=_('Upper memory size limit for '
'balloning.'))
arch = models.CharField(max_length=10, verbose_name=_('architecture'))
priority = models.IntegerField(help_text=_('instance priority'))
class Meta:
abstract = True
class NamedBaseResourceConfig(models.Model, BaseResourceConfigModel):
"""Pre-created, named base resource configurations.
"""
name = models.CharField(max_length=50, unique=True,
verbose_name=_('name'))
def __unicode__(self):
return self.name
class Interface(models.Model):
"""Network interface for an instance.
"""
vlan = models.ForeignKey(Vlan)
host = models.ForeignKey(Host)
instance = models.ForeignKey(Instance)
class InterfaceTemplate(models.Model):
"""Network interface template for an instance template.
"""
vlan = models.ForeignKey(Vlan)
managed = models.BooleanField()
template = models.ForeignKey(Template)
class Node(models.Model):
name = models.CharField(max_length=50, unique=True,
verbose_name=_('name'))
CPU = models.IntegerField(help_text=_('CPU cores.'))
RAM = models.IntegerField(help_text=_('Mebibytes of memory.'))
priority = models.IntegerField(help_text=_('node usage priority'))
host = models.ForeignKey(Host)
online = models.BooleanField(default=False)
class InstanceTemplate(models.Model, TimeStampedModel,
BaseResourceConfigModel):
"""Virtual machine template.
Every template has:
* a name and a description
* an optional parent template
* state of the template
* an OS name/description
* a method of access to the system
* default values of base resource configuration
* list of attached images
* set of interfaces
* time of creation and last modification
* ownership information
"""
STATES = [('NEW', _('new')), # template has just been created
('SAVING', _('saving')), # changes are being saved
('READY', _('ready'))] # template is ready for instantiation
ACCESS_METHODS = [('rdp', 'rdp'), ('nx', 'nx'), ('ssh', 'ssh'), ]
name = models.CharField(max_length=100, unique=True,
verbose_name=_('name'))
description = models.TextField(verbose_name=_('description'),
blank=True)
parent = models.ForeignKey('self', null=True, blank=True,
verbose_name=_('parent template'))
system = models.TextField(verbose_name=_('operating system'),
blank=True,
help_text=(_('Name of operating system in '
'format like "%s".') %
'Ubuntu 12.04 LTS Desktop amd64'))
access_method = models.CharField(max_length=10, choices=ACCESS_METHODS,
verbose_name=_('access method'))
state = models.CharField(max_length=10, choices=TEMPLATE_STATES,
default='NEW')
images = models.ManyToManyField(Image, verbose_name=_('images'),
related_name='template_set')
# TODO review
owner = models.ForeignKey(User, verbose_name=_('owner'),
related_name='template_set')
class Meta:
verbose_name = _('template')
verbose_name_plural = _('templates')
ordering = ['name', ]
def __unicode__(self):
return self.name
def running_instances(self):
"""Returns the number of running instances of the template.
"""
return self.instance_set.exclude(state='DONE').count()
@property
def os_type(self):
"""Get the type of the template's operating system.
"""
if self.access_method == 'rdp':
return "win"
else:
return "linux"
class Instance(models.Model, TimeStampedModel, BaseResourceConfigModel):
"""Virtual machine instance.
Every instance has:
* a name and a description
* an optional parent template
* associated share
* a generated password for login authentication
* time of deletion and time of suspension
* last boot timestamp
* host node
* current state (libvirt domain state) and operation (Celery job UUID)
* time of creation and last modification
* base resource configuration values
* ownership information
"""
STATES = [('NOSTATE', _('nostate')),
('RUNNING', _('running')),
('BLOCKED', _('blocked')),
('PAUSED', _('paused')),
('SHUTDOWN', _('shutdown')),
('SHUTOFF', _('shutoff')),
('CRASHED', _('crashed')),
('PMSUSPENDED', _('pmsuspended'))] # libvirt domain states
name = models.CharField(max_length=100, verbose_name=_('name'),
blank=True)
description = models.TextField(verbose_name=_('description'),
blank=True)
template = models.ForeignKey(Template, verbose_name=_('template'),
related_name='instance_set',
null=True, blank=True)
pw = models.CharField(max_length=20, verbose_name=_('password'),
help_text=_('Original password of instance'))
time_of_suspend = models.DateTimeField(default=None,
verbose_name=_('time of suspend'),
null=True, blank=True)
time_of_delete = models.DateTimeField(default=None,
verbose_name=_('time of delete'),
null=True, blank=True)
active_since = models.DateTimeField(null=True, blank=True,
verbose_name=_('active since'),
help_text=_('Time stamp of '
'successful boot '
'report.'))
share = models.ForeignKey('Share', blank=True, null=True,
verbose_name=_('share'),
related_name='instance_set')
node = models.ForeignKey(Node, verbose_name=_('host nose'),
related_name='instance_set')
state = models.CharField(max_length=20, choices=STATES,
default='NOSTATE')
operation = models.CharField(max_length=100, null=True, blank=True
verbose_name=_('operation'))
# TODO review fields below
owner = models.ForeignKey(User, verbose_name=_('owner'),
related_name='instance_set')
class Meta:
verbose_name = _('instance')
verbose_name_plural = _('instances')
ordering = ['pk', ]
def __unicode__(self):
return self.name
@models.permalink
def get_absolute_url(self):
return ('one.views.vm_show', None, {'iid': self.id})
@property
def primary_host(self):
if not hosts.exists():
return None
hs = hosts.filter(ipv6__is_null=False)
if hs.exists():
return hs[0]
hs = hosts.filter(shared_ip=False)
if hs.exists():
return hs[0]
return hosts.all()[0]
@property
def ipv4(self):
"""Primary IPv4 address of the instance."""
return self.primary_host.ipv4 if self.primary_host else None
@property
def ipv6(self):
"""Primary IPv6 address of the instance."""
return self.primary_host.ipv6 if self.primary_host else None
@property
def mac(self):
"""Primary MAC address of the instance."""
return self.primary_host.mac if self.primary_host else None
@property
def uptime(self):
"""Uptime of the instance."""
from datetime import datetime, timedelta
if self.active_since:
return (datetime.now().replace(tzinfo=None) -
self.active_since.replace(tzinfo=None))
else:
return timedelta()
def get_age(self):
"""Deprecated. Use uptime instead.
Get age of VM in seconds.
"""
return self.uptime.seconds
@property
def waiting(self):
return self.operation is not None
def get_port(self, use_ipv6=False):
"""Get public port number for default access method."""
# TODO move PROTOS to config
PROTOS = {"rdp": (3389,'tcp'), "nx": (22,'tcp'), "ssh": (22,'tcp')}
(port, proto) = PROTOS[self.template.access_method]
if self.primary_host:
endpoints = self.primary_host.get_public_endpoints(port, proto)
endpoint = endpoints['ipv6'] if use_ipv6 else endpoints['ipv4']
return endpoint[1] if endpoint else None
else:
return None
def get_connect_host(self, use_ipv6=False):
"""Get public hostname."""
if self.firewall_host is None:
return _('None')
proto = 'ipv6' if use_ipv6 else 'ipv4'
return self.firewall_host.get_hostname(proto=proto)
def get_connect_uri(self, use_ipv6=False):
"""Get access parameters in URI format."""
try:
proto = self.template.access_type
if proto == 'ssh':
proto = 'sshterm'
port = self.get_port(use_ipv6=use_ipv6)
host = self.get_connect_host(use_ipv6=use_ipv6)
pw = self.pw
return ("%(proto)s:cloud:%(pw)s:%(host)s:%(port)d" %
{"port": port, "proto": proto, "pw": pw,
"host": host})
except:
return
@staticmethod
def _create_context(pw, hostname, smb_password, ssh_private_key, owner,
token, extra):
"""Return XML context configuration with given parameters."""
ctx = u'''
<SOURCE>web</SOURCE>
<HOSTNAME>%(hostname)s</HOSTNAME>
<NEPTUN>%(neptun)s</NEPTUN>
<USERPW>%(pw)s</USERPW>
<SMBPW>%(smbpw)s</SMBPW>
<SSHPRIV>%(sshkey)s</SSHPRIV>
<BOOTURL>%(booturl)s</BOOTURL>
<SERVER>store.cloud.ik.bme.hu</SERVER>
%(extra)s
''' % {
"pw": escape(pw),
"hostname": escape(hostname),
"smbpw": escape(smb_password),
"sshkey": escape(ssh_private_key),
"neptun": escape(owner),
"booturl": "%sb/%s/" % (CLOUD_URL, token),
"extra": extra
}
return ctx
def _create_host(self, hostname, occi_result):
"""Create firewall host for recently submitted Instance."""
host = Host(
vlan=Vlan.objects.get(name=self.template.network.name),
owner=self.owner, hostname=hostname,
mac=occi_result['interfaces'][0]['mac'],
ipv4=occi_result['interfaces'][0]['ip'], ipv6='auto',
)
if self.template.network.nat:
host.pub_ipv4 = Vlan.objects.get(
name=self.template.network.name).snat_ip
host.shared_ip = True
try:
host.save()
except:
for i in Host.objects.filter(ipv4=host.ipv4).all():
logger.warning('Delete orphan fw host (%s) of %s.' % (i, self))
i.delete()
for i in Host.objects.filter(mac=host.mac).all():
logger.warning('Delete orphan fw host (%s) of %s.' % (i, self))
i.delete()
host.save()
host.enable_net()
port = {"rdp": 3389, "nx": 22, "ssh": 22}[self.template.access_type]
host.add_port("tcp", self.get_port(), port)
self.firewall_host = host
self.save()
@classmethod
def submit(cls, template, owner, extra="", share=None):
"""Submit a new instance to OpenNebula."""
inst = Instance(pw=pwgen(), template=template, owner=owner,
share=share, state='PENDING', waiting=True)
inst.save()
hostname = u"%d" % (inst.id, )
token = signing.dumps(inst.id, salt='activate')
try:
details = owner.cloud_details
except:
details = UserCloudDetails(user=owner)
details.save()
ctx = cls._create_context(inst.pw, hostname, details.smb_password,
details.ssh_private_key, owner.username,
token, extra)
try:
from .tasks import CreateInstanceTask
x = CreateInstanceTask.delay(
name=u"%s %d" % (owner.username, inst.id),
instance_type=template.instance_type.name,
disk_id=int(template.disk.id),
network_id=int(template.network.id),
ctx=ctx,
)
res = x.get(timeout=10)
res['one_id']
except:
inst.delete()
raise Exception("Unable to create VM instance.")
inst.one_id = res['one_id']
inst.ip = res['interfaces'][0]['ip']
inst.name = ("%(neptun)s %(template)s (%(id)d)" %
{'neptun': owner.username, 'template': template.name,
'id': inst.one_id})
inst.save()
inst._create_host(hostname, res)
return inst
def one_delete(self):
"""Delete host in OpenNebula."""
if self.template.state != "DONE":
self.check_if_is_save_as_done()
if self.one_id and self.state != 'DONE':
self.waiting = True
self.save()
from .tasks import DeleteInstanceTask
DeleteInstanceTask.delay(one_id=self.one_id)
self.firewall_host_delete()
def firewall_host_delete(self):
if self.firewall_host:
h = self.firewall_host
self.firewall_host = None
try:
self.save()
except:
pass
h.delete()
def _change_state(self, new_state):
"""Change host state in OpenNebula."""
from .tasks import ChangeInstanceStateTask
ChangeInstanceStateTask.delay(one_id=self.one_id, new_state=new_state)
self.waiting = True
self.save()
def stop(self):
self._change_state("STOPPED")
def resume(self):
self._change_state("RESUME")
def poweroff(self):
self._change_state("POWEROFF")
def restart(self):
self._change_state("RESET")
self.waiting = False
self.save()
def renew(self, which='both'):
if which in ['suspend', 'both']:
self.time_of_suspend = self.share_type['suspendx']
if which in ['delete', 'both']:
self.time_of_delete = self.share_type['deletex']
if not (which in ['suspend', 'delete', 'both']):
raise ValueError('No such expiration type.')
self.save()
@property
def share_type(self):
if self.share:
return self.share.get_type()
else:
return Share.extend_type(DEFAULT_TYPE)
def save_as(self):
"""Save image and shut down."""
imgname = "template-%d-%d" % (self.template.id, self.id)
from .tasks import SaveAsTask
SaveAsTask.delay(one_id=self.one_id, new_img=imgname)
self._change_state("SHUTDOWN")
self.save()
t = self.template
t.state = 'SAVING'
t.save()
def check_if_is_save_as_done(self):
if self.state != 'DONE':
return False
Disk.update(delete=False)
imgname = "template-%d-%d" % (self.template.id, self.id)
disks = Disk.objects.filter(name=imgname)
if len(disks) != 1:
return False
self.template.disk_id = disks[0].id
self.template.state = 'READY'
self.template.save()
self.firewall_host_delete()
return True
def delete_instance_pre(sender, instance, using, **kwargs):
if instance.state != 'DONE':
instance.one_delete()
pre_delete.connect(delete_instance_pre, sender=Instance,
dispatch_uid="delete_instance_pre")
from django.test import TestCase
from .models import Template
class TemplateTestCase(TestCase):
def test_template_creation(self):
template = Template(name='My first template',
access_method='ssh', ) # TODO add images & net
# Create your views here.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment