Commit 6c393877 by Dudás Ádám

one: enforcing guidelines

parent 059a0c45
......@@ -19,7 +19,7 @@ def reload_firewall(request):
if request.user.is_authenticated():
if request.user.is_superuser:
html = ((_("Dear %s, you've signed in as administrator!") %
request.user.username) + "<br>" +
request.user.username) + "<br />" +
_("Reloading in 10 seconds..."))
ReloadTask.delay()
else:
......
......@@ -22,38 +22,39 @@ import subprocess, tempfile, os, stat, re, base64, struct
logger = logging.getLogger(__name__)
pwgen = User.objects.make_random_password
"""
User creation hook: create cloud details object
"""
def create_user_profile(sender, instance, created, **kwargs):
"""User creation hook: create cloud details object"""
if created:
d = UserCloudDetails(user=instance)
d.clean()
d.save()
post_save.connect(create_user_profile, sender=User)
d = UserCloudDetails(user=instance)
d.clean()
d.save()
post_save.connect(create_user_profile, sender=User)
"""
Cloud related details of a user
"""
class UserCloudDetails(models.Model):
user = models.ForeignKey(User, null=False, blank=False, unique=True, verbose_name=_('user'))
"""Cloud related details of a user."""
user = models.ForeignKey(User, null=False, blank=False, unique=True,
verbose_name=_('user'))
smb_password = models.CharField(max_length=20,
verbose_name=_('Samba password'),
help_text=_('Generated password for accessing store from Windows.'))
ssh_key = models.ForeignKey('SshKey', null=True, verbose_name=_('SSH key (public)'),
help_text=_('Generated SSH public key for accessing store from Linux.'))
ssh_private_key = models.TextField(verbose_name=_('SSH key (private)'), null=True,
help_text=_('Generated SSH private key for accessing store from Linux.'))
share_quota = models.IntegerField(verbose_name=_('share quota'), default=0)
instance_quota = models.IntegerField(verbose_name=_('instance quota'), default=20)
disk_quota = models.IntegerField(verbose_name=_('disk quota'), default=2048,
help_text=_('Disk quota in mebibytes.'))
"""
Delete old SSH key pair and generate new one.
"""
help_text=_('Generated password for accessing store from '
'Windows.'))
ssh_key = models.ForeignKey('SshKey', null=True,
verbose_name=_('SSH key (public)'),
help_text=_('Generated SSH public key for accessing store from '
'Linux.'))
ssh_private_key = models.TextField(verbose_name=_('SSH key (private)'),
null=True, help_text=_('Generated SSH private key for '
'accessing store from Linux.'))
share_quota = models.IntegerField(verbose_name=_('share quota'),
default=0)
instance_quota = models.IntegerField(verbose_name=_('instance quota'),
default=20)
disk_quota = models.IntegerField(verbose_name=_('disk quota'),
default=2048, help_text=_('Disk quota in mebibytes.'))
def reset_keys(self):
"""Delete old SSH key pair and generate new one."""
pri, pub = keygen()
self.ssh_private_key = pri
......@@ -65,10 +66,8 @@ class UserCloudDetails(models.Model):
self.ssh_key_id = self.ssh_key.id
self.save()
"""
Generate new Samba password.
"""
def reset_smb(self):
"""Generate new Samba password."""
self.smb_password = pwgen()
def get_weighted_instance_count(self):
......@@ -78,15 +77,14 @@ class UserCloudDetails(models.Model):
c = c + i.template.instance_type.credit
return c
def get_instance_pc(self):
return 100*self.get_weighted_instance_count()/self.instance_quota
return 100 * self.get_weighted_instance_count() / self.instance_quota
def get_weighted_share_count(self):
c = 0
for i in Share.objects.filter(owner=self.user).all():
c = c + i.template.instance_type.credit * i.instance_limit
return c
def get_share_pc(self):
return 100*self.get_weighted_share_count()/self.share_quota
return 100 * self.get_weighted_share_count() / self.share_quota
def set_quota(sender, instance, created, **kwargs):
if not StoreApi.userexist(instance.user.username):
......@@ -98,11 +96,13 @@ def set_quota(sender, instance, created, **kwargs):
key_list.append(key.key)
except:
pass
#Create user
if not StoreApi.createuser(instance.user.username, password, key_list, quota):
# Create user
if not StoreApi.createuser(instance.user.username, password,
key_list, quota):
pass
else:
StoreApi.set_quota(instance.user.username, instance.disk_quota*1024)
StoreApi.set_quota(instance.user.username,
instance.disk_quota * 1024)
post_save.connect(set_quota, sender=UserCloudDetails)
def reset_keys(sender, instance, created, **kwargs):
......@@ -112,10 +112,8 @@ def reset_keys(sender, instance, created, **kwargs):
post_save.connect(reset_keys, sender=UserCloudDetails)
"""
Validate OpenSSH keys (length and type).
"""
class OpenSshKeyValidator(object):
"""Validate OpenSSH keys (length and type)."""
valid_types = ['ssh-rsa', 'ssh-dsa']
def __init__(self, types=None):
......@@ -127,7 +125,8 @@ class OpenSshKeyValidator(object):
value = "%s comment" % value
type, key_string, comment = value.split(None, 2)
if type not in self.valid_types:
raise ValidationError(_('OpenSSH key type %s is not supported.') % type)
raise ValidationError(_('OpenSSH key type %s is not '
'supported.') % type)
data = base64.decodestring(key_string)
int_len = 4
str_len = struct.unpack('>I', data[:int_len])[0]
......@@ -138,15 +137,15 @@ class OpenSshKeyValidator(object):
except:
raise ValidationError(_('Invalid OpenSSH public key.'))
"""
SSH public key (in OpenSSH format).
"""
class SshKey(models.Model):
"""SSH public key (in OpenSSH format)."""
user = models.ForeignKey(User, null=False, blank=False)
key = models.CharField(max_length=2000, verbose_name=_('SSH key'),
help_text=_('<a href="/info/ssh/">SSH public key in OpenSSH format</a> used for shell and store login '
'(2048+ bit RSA preferred). Example: <code>ssh-rsa AAAAB...QtQ== '
'john</code>.'), validators=[OpenSshKeyValidator()])
help_text=_('<a href="/info/ssh/">SSH public key in OpenSSH '
'format</a> used for shell and store login '
'(2048+ bit RSA preferred). Example: '
'<code>ssh-rsa AAAAB...QtQ== john</code>.'),
validators=[OpenSshKeyValidator()])
def __unicode__(self):
try:
......@@ -156,62 +155,75 @@ class SshKey(models.Model):
return u"%s (%s)" % (keycomment, self.user)
TEMPLATE_STATES = (("INIT", _('init')), ("PREP", _('perparing')), ("SAVE", _('saving')), ("READY", _('ready')))
TYPES = {"LAB": {"verbose_name": _('lab'), "id": "LAB", "suspend": td(hours=5), "delete": td(days=15), "help_text": _('For lab or home work with short life time.')},
"PROJECT": {"verbose_name": _('project'), "id": "PROJECT", "suspend": td(weeks=5), "delete": td(days=366/2), "help_text": _('For project work.')},
"SERVER": {"verbose_name": _('server'), "id": "SERVER", "suspend": td(days=365), "delete": None, "help_text": _('For long term server use.')},
TEMPLATE_STATES = (("INIT", _('init')), ("PREP", _('perparing')),
("SAVE", _('saving')), ("READY", _('ready')))
TYPES = {"LAB": {"verbose_name": _('lab'), "id": "LAB",
"suspend": td(hours=5), "delete": td(days=15),
"help_text": _('For lab or homework with short lifetime.')},
"PROJECT": {"verbose_name": _('project'), "id": "PROJECT",
"suspend": td(weeks=5), "delete": td(days=366/2),
"help_text": _('For project work.')},
"SERVER": {"verbose_name": _('server'), "id": "SERVER",
"suspend": td(days=365), "delete": None,
"help_text": _('For long-term server use.')},
}
TYPES_L = sorted(TYPES.values(), key=lambda m: m["suspend"])
TYPES_C = tuple([(i[0], i[1]["verbose_name"]) for i in TYPES.items()])
class Share(models.Model):
name = models.CharField(max_length=100, verbose_name=_('name'))
description = models.TextField(verbose_name=_('description'))
template = models.ForeignKey('Template', null=False, blank=False)
group = models.ForeignKey(Group, null=False, blank=False)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
type = models.CharField(choices=TYPES_C, max_length=10, blank=False, null=False)
created_at = models.DateTimeField(auto_now_add=True,
verbose_name=_('created at'))
type = models.CharField(choices=TYPES_C, max_length=10, blank=False,
null=False)
instance_limit = models.IntegerField(verbose_name=_('instance limit'),
help_text=_('Maximal count of instances launchable for this share.'))
help_text=_('Maximal count of instances launchable for this '
'share.'))
per_user_limit = models.IntegerField(verbose_name=_('per user limit'),
help_text=_('Maximal count of instances launchable by a single user.'))
help_text=_('Maximal count of instances launchable by a single '
'user.'))
owner = models.ForeignKey(User, null=True, blank=True)
def get_type(self):
t = TYPES[self.type]
t['deletex'] = datetime.now() + td(seconds=1) + t['delete'] if t['delete'] else None
t['suspendx'] = datetime.now() + td(seconds=1) + t['suspend'] if t['suspend'] else None
t['deletex'] = (datetime.now() + td(seconds=1) + t['delete']
if t['delete'] else None)
t['suspendx'] = (datetime.now() + td(seconds=1) + t['suspend']
if t['suspend'] else None)
return t
def get_running_or_stopped(self, user=None):
running = Instance.objects.all().exclude(state='DONE').filter(share=self)
running = (Instance.objects.all().exclude(state='DONE')
.filter(share=self))
if user:
return running.filter(owner=user).count()
else:
return running.count()
def get_running(self, user=None):
running = Instance.objects.all().exclude(state='DONE').exclude(state='STOPPED').filter(share=self)
running = (Instance.objects.all().exclude(state='DONE')
.exclude(state='STOPPED').filter(share=self))
if user:
return running.filter(owner=user).count()
else:
return running.count()
def get_instance_pc(self):
return float(self.get_running()) / self.instance_limit * 100
"""
Virtual disks automatically synchronized with OpenNebula.
"""
class Disk(models.Model):
name = models.CharField(max_length=100, unique=True, verbose_name=_('name'))
"""Virtual disks automatically synchronized with OpenNebula."""
name = models.CharField(max_length=100, unique=True,
verbose_name=_('name'))
"""
Get and register virtual disks from OpenNebula.
"""
@classmethod
def update(cls):
"""Get and register virtual disks from OpenNebula."""
import subprocess
proc = subprocess.Popen(["/opt/occi.sh",
"storage", "list"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
proc = subprocess.Popen(["/opt/occi.sh", "storage", "list"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = proc.communicate()
from xml.dom.minidom import parse, parseString
x = parseString(out)
......@@ -235,22 +247,21 @@ class Disk(models.Model):
class Meta:
ordering = ['name']
"""
Virtual networks automatically synchronized with OpenNebula.
"""
class Network(models.Model):
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.'))
"""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.'))
"""
Get and register virtual networks from OpenNebula.
"""
@classmethod
def update(cls):
"""Get and register virtual networks from OpenNebula."""
import subprocess
proc = subprocess.Popen(["/opt/occi.sh",
"network", "list"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
proc = subprocess.Popen(["/opt/occi.sh", "network", "list"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = proc.communicate()
from xml.dom.minidom import parse, parseString
x = parseString(out)
......@@ -273,10 +284,8 @@ class Network(models.Model):
class Meta:
ordering = ['name']
"""
Instance types in OCCI configuration (manually synchronized).
"""
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.'))
......@@ -288,23 +297,24 @@ class InstanceType(models.Model):
class Meta:
ordering = ['credit']
TEMPLATE_STATES = (('NEW', _('new')),
('SAVING', _('saving')), ('READY', _('ready')), )
"""
Virtual machine template specifying OS, disk, type and network.
"""
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'))
instance_type = models.ForeignKey(InstanceType, verbose_name=_('instance type'))
instance_type = models.ForeignKey(InstanceType,
verbose_name=_('instance type'))
network = models.ForeignKey(Network, verbose_name=_('network'))
owner = models.ForeignKey(User, verbose_name=_('owner'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
state = models.CharField(max_length=10, choices=TEMPLATE_STATES, default='NEW')
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)
......@@ -336,56 +346,57 @@ class Template(models.Model):
verbose_name = _('template')
verbose_name_plural = _('templates')
"""
Virtual machine instance.
"""
class Instance(models.Model):
"""Virtual machine instance."""
name = models.CharField(max_length=100, unique=True,
verbose_name=_('name'), null=True, blank=True)
ip = models.IPAddressField(blank=True, null=True, verbose_name=_('IP address'))
ip = models.IPAddressField(blank=True, null=True,
verbose_name=_('IP address'))
template = models.ForeignKey(Template, verbose_name=_('template'))
owner = models.ForeignKey(User, verbose_name=_('owner'))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'))
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')
('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'))
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'))
time_of_suspend = models.DateTimeField(default=None, verbose_name=_('time of suspend'), null=True, blank=False)
time_of_delete = models.DateTimeField(default=None, verbose_name=_('time of delete'), null=True, blank=False)
firewall_host = models.ForeignKey(Host, blank=True, null=True,
verbose_name=_('host in firewall'))
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'))
time_of_suspend = models.DateTimeField(default=None,
verbose_name=_('time of suspend'), null=True, blank=False)
time_of_delete = models.DateTimeField(default=None,
verbose_name=_('time of delete'), null=True, blank=False)
waiting = models.BooleanField(default=False)
"""
Get public port number for default access method.
"""
def get_port(self):
"""Get public port number for default access method."""
proto = self.template.access_type
if self.template.network.nat:
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]
"""
Get public hostname.
"""
def get_connect_host(self):
"""Get public hostname."""
if self.template.network.nat:
return 'cloud'
else:
return self.ip
"""
Get access parameters in URI format.
"""
def get_connect_uri(self):
"""Get access parameters in URI format."""
try:
proto = self.template.access_type
if proto == 'ssh':
......@@ -393,31 +404,31 @@ class Instance(models.Model):
port = self.get_port()
host = self.get_connect_host()
pw = self.pw
return "%(proto)s:cloud:%(pw)s:%(host)s:%(port)d" % {"port": port,
"proto": proto, "host": self.firewall_host.pub_ipv4, "pw": pw}
return ("%(proto)s:cloud:%(pw)s:%(host)s:%(port)d" %
{"port": port, "proto": proto, "pw": pw,
"host": self.firewall_host.pub_ipv4})
except:
return
def __unicode__(self):
return self.name
"""
Get and update VM state from OpenNebula.
"""
def update_state(self):
"""Get and update VM state from OpenNebula."""
import subprocess
if not self.one_id:
return
proc = subprocess.Popen(["/opt/occi.sh", "compute", "show",
"%d"%self.one_id], stdout=subprocess.PIPE)
"%d" % self.one_id], stdout=subprocess.PIPE)
(out, err) = proc.communicate()
x = None
old_state = self.state
try:
from xml.dom.minidom import parse, parseString
x = parseString(out)
self.vnet_ip = x.getElementsByTagName("IP")[0].childNodes[0].nodeValue.split('.')[3]
self.vnet_ip = (x.getElementsByTagName("IP")[0].childNodes[0]
.nodeValue.split('.')[3])
state = x.getElementsByTagName("STATE")[0].childNodes[0].nodeValue
self.state = state
except:
......@@ -429,10 +440,8 @@ class Instance(models.Model):
self.check_if_is_save_as_done()
return x
"""
Get age of VM in seconds.
"""
def get_age(self):
"""Get age of VM in seconds."""
from datetime import datetime
age = 0
try:
......@@ -446,11 +455,9 @@ class Instance(models.Model):
def get_absolute_url(self):
return ('vm_show', None, {'iid':self.id})
"""
Submit a new instance to OpenNebula.
"""
@classmethod
def submit(cls, template, owner, extra="", share=None):
"""Submit a new instance to OpenNebula."""
from django.template.defaultfilters import escape
out = ""
inst = Instance(pw=pwgen(), template=template, owner=owner, share=share)
......@@ -498,9 +505,8 @@ class Instance(models.Model):
f.write(tpl)
f.close()
import subprocess
proc = subprocess.Popen(["/opt/occi.sh",
"compute", "create",
f.name], stdout=subprocess.PIPE)
proc = subprocess.Popen(["/opt/occi.sh", "compute", "create",
f.name], stdout=subprocess.PIPE)
(out, err) = proc.communicate()
os.unlink(f.name)
from xml.dom.minidom import parse, parseString
......@@ -508,12 +514,16 @@ class Instance(models.Model):
x = parseString(out)
except:
raise Exception("Unable to create VM instance.")
inst.one_id = int(x.getElementsByTagName("ID")[0].childNodes[0].nodeValue)
inst.one_id = int(x.getElementsByTagName("ID")[0].childNodes[0]
.nodeValue)
inst.ip = x.getElementsByTagName("IP")[0].childNodes[0].nodeValue
inst.name = "%(neptun)s %(template)s (%(id)d)" % {'neptun': owner.username, 'template': template.name, 'id': inst.one_id}
inst.name = ("%(neptun)s %(template)s (%(id)d)" %
{'neptun': owner.username, 'template': template.name,
'id': inst.one_id})
inst.save()
inst.update_state()
host = Host(vlan=Vlan.objects.get(name=template.network.name), owner=owner, shared_ip=True)
host = Host(vlan=Vlan.objects.get(name=template.network.name),
owner=owner, shared_ip=True)
host.hostname = u"id-%d_user-%s" % (inst.id, owner.username)
host.mac = x.getElementsByTagName("MAC")[0].childNodes[0].nodeValue
host.ipv4 = inst.ip
......@@ -527,17 +537,16 @@ class Instance(models.Model):
i.delete()
host.save()
host.enable_net()
host.add_port("tcp", inst.get_port(), {"rdp": 3389, "nx": 22, "ssh": 22}[inst.template.access_type])
host.add_port("tcp", inst.get_port(), {"rdp": 3389, "nx": 22,
"ssh": 22}[inst.template.access_type])
inst.firewall_host=host
inst.save()
return inst
"""
Delete host in OpenNebula.
"""
def one_delete(self):
proc = subprocess.Popen(["/opt/occi.sh", "compute",
"delete", "%d"%self.one_id], stdout=subprocess.PIPE)
"""Delete host in OpenNebula."""
proc = subprocess.Popen(["/opt/occi.sh", "compute", "delete",
"%d" % self.one_id], stdout=subprocess.PIPE)
(out, err) = proc.communicate()
self.firewall_host_delete()
......@@ -561,17 +570,14 @@ class Instance(models.Model):
f.write(tpl)
f.close()
import subprocess
proc = subprocess.Popen(["/opt/occi.sh",
"compute", "update",
proc = subprocess.Popen(["/opt/occi.sh", "compute", "update",
f.name], stdout=subprocess.PIPE)
(out, err) = proc.communicate()
os.unlink(f.name)
print "out: " + out
"""
Change host state in OpenNebula.
"""
def _change_state(self, new_state):
"""Change host state in OpenNebula."""
self._update_vm("<STATE>" + new_state + "</STATE>")
def stop(self):
......@@ -593,9 +599,7 @@ class Instance(models.Model):
raise ValueError('No such expiration type.')
self.save()
def save_as(self):
"""
Save image and shut down.
"""
"""Save image and shut down."""
imgname = "template-%d-%d" % (self.template.id, self.id)
self._update_vm('<DISK id="0"><SAVE_AS name="%s"/></DISK>' % imgname)
self._change_state("SHUTDOWN")
......@@ -630,7 +634,8 @@ def delete_instance(sender, instance, using, **kwargs):
instance.firewall_host_delete()
except:
pass
post_delete.connect(delete_instance, sender=Instance, dispatch_uid="delete_instance")
post_delete.connect(delete_instance, sender=Instance,
dispatch_uid="delete_instance")
def delete_instance_pre(sender, instance, using, **kwargs):
try:
......@@ -638,5 +643,6 @@ def delete_instance_pre(sender, instance, using, **kwargs):
instance.check_if_is_save_as_done()
except:
pass
pre_delete.connect(delete_instance_pre, sender=Instance, dispatch_uid="delete_instance_pre")
pre_delete.connect(delete_instance_pre, sender=Instance,
dispatch_uid="delete_instance_pre")
......@@ -30,5 +30,3 @@ def keygen(length=1024):
pub = 'ssh-rsa %s' % (
base64.b64encode(base64.b16decode(ssh_rsa.upper())), )
return key.exportKey(), "%s %s" % (pub, "cloud-%s" % date.today())
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