Commit ac0e2797 by Kálmán Viktor

Merge branch 'master' into network-gui

Conflicts:
	cloud/urls.py
parents 689327cf 0e6e870e
import json
import subprocess
from django.conf import settings
from django.core.cache import cache
def process_debug(req):
return {'DEBUG': settings.DEBUG}
def process_stat(req):
if settings.STAT_DEBUG:
stat = {
......@@ -28,6 +27,7 @@ def process_stat(req):
'cloud_stat': stat,
}
def process_release(req):
return {
'release': settings.RELEASE,
......
......@@ -274,11 +274,14 @@ DELETE_VM = True
EMAIL_HOST = '152.66.243.92' # giccero ipv4
CLOUD_URL = 'https://cloud.ik.bme.hu/'
try:
current_dir = os.getcwd()
os.chdir('/opt/webadmin/cloud/')
RELEASE = subprocess.check_output(
['/usr/bin/git', 'describe', '--tags', '--abbrev=4'])
except:
RELEASE = 'n/a'
finally:
os.chdir(current_dir)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
......
# coding=utf8
# Django development settings for cloud project.
from .base import *
from .base import * # NOQA
DEBUG = True
TEMPLATE_DEBUG = DEBUG
......@@ -8,11 +8,11 @@ EMAIL_HOST = "localhost"
EMAIL_PORT = 1025
ADMINS = (
('Ory, Mate', 'orymate@localhost'),
)
('Ory, Mate', 'orymate@localhost'),
)
MANAGERS = (
('Ory Mate', 'maat@localhost'),
)
('Ory Mate', 'maat@localhost'),
)
INSTALLED_APPS += ("debug_toolbar", )
MIDDLEWARE_CLASSES += ("debug_toolbar.middleware.DebugToolbarMiddleware", )
INTERNAL_IPS = [('2001:738:2001:4031:5:253:%d:0' % i) for i in xrange(1, 100)]
......
# coding=utf8
# Django production settings for cloud project.
from .base import *
from .base import * # NOQA
DEBUG = False
TEMPLATE_DEBUG = DEBUG
......
......@@ -3,6 +3,7 @@ from django.conf.urls import patterns, include, url
from django.contrib import admin
admin.autodiscover()
js_info_dict = {
'packages': ('one', ),
}
......
......@@ -23,6 +23,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cloud.settings.prod")
from django.core.wsgi import get_wsgi_application
_application = get_wsgi_application()
def application(environ, start_response):
# copy DJANGO_* wsgi-env vars to process-env
for i in environ.keys():
......
# -*- coding: utf8 -*-
from django.contrib import admin
from firewall.models import *
from firewall.models import (Rule, Host, Vlan, Group, VlanGroup, Firewall,
Domain, Record, Blacklist)
from django import contrib
class RuleInline(contrib.admin.TabularInline):
model = Rule
class RecordInline(contrib.admin.TabularInline):
model = Record
class HostAdmin(admin.ModelAdmin):
list_display = ('hostname', 'vlan', 'ipv4', 'ipv6', 'pub_ipv4', 'mac',
'shared_ip', 'owner', 'description', 'reverse', 'list_groups')
'shared_ip', 'owner', 'description', 'reverse',
'list_groups')
ordering = ('hostname', )
list_filter = ('owner', 'vlan', 'groups')
search_fields = ('hostname', 'description', 'ipv4', 'ipv6', 'mac')
......@@ -26,42 +30,46 @@ class HostAdmin(admin.ModelAdmin):
names = [group.name for group in instance.groups.all()]
return u', '.join(names)
class HostInline(contrib.admin.TabularInline):
model = Host
fields = ('hostname', 'ipv4', 'ipv6', 'pub_ipv4', 'mac', 'shared_ip',
'owner', 'reverse')
'owner', 'reverse')
class VlanAdmin(admin.ModelAdmin):
list_display = ('vid', 'name', 'ipv4', 'net_ipv4', 'ipv6', 'net_ipv6',
'description', 'domain', 'snat_ip', )
'description', 'domain', 'snat_ip', )
ordering = ('vid', )
inlines = (RuleInline, )
class RuleAdmin(admin.ModelAdmin):
list_display = ('r_type', 'color_desc', 'owner', 'extra', 'direction',
'accept', 'proto', 'sport', 'dport', 'nat', 'nat_dport', 'used_in')
'accept', 'proto', 'sport', 'dport', 'nat',
'nat_dport', 'used_in')
list_filter = ('r_type', 'vlan', 'owner', 'direction', 'accept',
'proto', 'nat')
'proto', 'nat')
def color_desc(self, instance):
"""Returns a colorful description of the instance."""
return (u'<span style="color: #FF0000;">[%(type)s]</span> '
u'%(src)s<span style="color: #0000FF;"> ▸ </span>%(dst)s '
u'%(para)s %(desc)s') % {
'type': instance.r_type,
'src': (instance.foreign_network.name
if instance.direction == '1' else instance.r_type),
'dst': (instance.r_type if instance.direction == '1'
else instance.foreign_network.name),
'para': (u'<span style="color: #00FF00;">' +
'type': instance.r_type,
'src': (instance.foreign_network.name
if instance.direction == '1' else instance.r_type),
'dst': (instance.r_type if instance.direction == '1'
else instance.foreign_network.name),
'para': (u'<span style="color: #00FF00;">' +
(('proto=%s ' % instance.proto)
if instance.proto else '') +
(('sport=%s ' % instance.sport)
if instance.sport else '') +
(('dport=%s ' % instance.dport)
if instance.dport else '') +
'</span>'),
'desc': instance.description}
'</span>'),
'desc': instance.description}
color_desc.allow_tags = True
@staticmethod
......@@ -73,7 +81,7 @@ class RuleAdmin(admin.ModelAdmin):
@staticmethod
def used_in(instance):
for field in [instance.vlan, instance.vlangroup, instance.host,
instance.hostgroup, instance.firewall]:
instance.hostgroup, instance.firewall]:
if field:
return unicode(field) + ' ' + field._meta.object_name
......@@ -81,16 +89,20 @@ class RuleAdmin(admin.ModelAdmin):
class AliasAdmin(admin.ModelAdmin):
list_display = ('alias', 'host')
class GroupAdmin(admin.ModelAdmin):
list_display = ('name', 'owner', 'description')
inlines = (RuleInline, )
class FirewallAdmin(admin.ModelAdmin):
inlines = (RuleInline, )
class DomainAdmin(admin.ModelAdmin):
list_display = ('name', 'owner')
class RecordAdmin(admin.ModelAdmin):
list_display = ('name_', 'type', 'address_', 'ttl', 'host', 'owner')
......@@ -104,6 +116,7 @@ class RecordAdmin(admin.ModelAdmin):
a = instance.get_data()
return a['name'] if a else None
class BlacklistAdmin(admin.ModelAdmin):
list_display = ('ipv4', 'reason', 'created_at', 'modified_at')
......@@ -116,4 +129,3 @@ admin.site.register(Firewall, FirewallAdmin)
admin.site.register(Domain, DomainAdmin)
admin.site.register(Record, RecordAdmin)
admin.site.register(Blacklist, BlacklistAdmin)
......@@ -6,12 +6,14 @@ from django.utils.ipv6 import is_valid_ipv6_address
from south.modelsinspector import add_introspection_rules
import re
mac_re = re.compile(r'^([0-9a-fA-F]{2}(:|$)){6}$')
alfanum_re = re.compile(r'^[A-Za-z0-9_-]+$')
domain_re = re.compile(r'^([A-Za-z0-9_-]\.?)+$')
ipv4_re = re.compile('^[0-9]+\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
reverse_domain_re = re.compile(r'^(%\([abcd]\)d|[a-z0-9.-])+$')
class MACAddressFormField(fields.RegexField):
default_error_messages = {
'invalid': _(u'Enter a valid MAC address.'),
......@@ -20,8 +22,10 @@ class MACAddressFormField(fields.RegexField):
def __init__(self, *args, **kwargs):
super(MACAddressFormField, self).__init__(mac_re, *args, **kwargs)
class MACAddressField(models.Field):
empty_strings_allowed = False
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 17
super(MACAddressField, self).__init__(*args, **kwargs)
......@@ -35,58 +39,68 @@ class MACAddressField(models.Field):
return super(MACAddressField, self).formfield(**defaults)
add_introspection_rules([], ["firewall\.fields\.MACAddressField"])
def val_alfanum(value):
"""Validate whether the parameter is a valid alphanumeric value."""
if not alfanum_re.match(value):
raise ValidationError(_(u'%s - only letters, numbers, underscores '
'and hyphens are allowed!') % value)
'and hyphens are allowed!') % value)
def is_valid_domain(value):
"""Check whether the parameter is a valid domain name."""
return domain_re.match(value) is not None
def val_domain(value):
"""Validate whether the parameter is a valid domin name."""
if not is_valid_domain(value):
raise ValidationError(_(u'%s - invalid domain name') % value)
def is_valid_reverse_domain(value):
"""Check whether the parameter is a valid reverse domain name."""
return reverse_domain_re.match(value) is not None
def val_reverse_domain(value):
"""Validate whether the parameter is a valid reverse domain name."""
if not is_valid_reverse_domain(value):
raise ValidationError(u'%s - invalid reverse domain name' % value)
def is_valid_ipv4_address(value):
"""Check whether the parameter is a valid IPv4 address."""
return ipv4_re.match(value) is not None
def val_ipv4(value):
"""Validate whether the parameter is a valid IPv4 address."""
if not is_valid_ipv4_address(value):
raise ValidationError(_(u'%s - not an IPv4 address') % value)
def val_ipv6(value):
"""Validate whether the parameter is a valid IPv6 address."""
if not is_valid_ipv6_address(value):
raise ValidationError(_(u'%s - not an IPv6 address') % value)
def val_mx(value):
"""Validate whether the parameter is a valid MX address definition.
Expected form is <priority>:<hostname>.
"""
mx = self.address.split(':', 1)
mx = value.split(':', 1)
if not (len(mx) == 2 and mx[0].isdigit() and
domain_re.match(mx[1])):
raise ValidationError(_("Bad MX address format. "
"Should be: <priority>:<hostname>"))
def ipv4_2_ipv6(ipv4):
"""Convert IPv4 address string to IPv6 address string."""
val_ipv4(ipv4)
m = ipv4_re.match(ipv4)
return ("2001:738:2001:4031:%s:%s:%s:0" %
(m.group(1), m.group(2), m.group(3)))
(m.group(1), m.group(2), m.group(3)))
......@@ -4,7 +4,9 @@ from django.contrib.auth.models import User
from django.db import models
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
from firewall.fields import *
from firewall.fields import (MACAddressField, val_alfanum, val_reverse_domain,
val_domain, val_ipv4, val_ipv6, val_mx,
ipv4_2_ipv6)
from django.core.validators import MinValueValidator, MaxValueValidator
import django.conf
from django.db.models.signals import post_save
......@@ -85,7 +87,7 @@ class Rule(models.Model):
"(if type is vlan)."))
vlangroup = models.ForeignKey('VlanGroup', related_name="rules",
blank=True, null=True, verbose_name=_(
"vlan group"),
"vlan group"),
help_text=_("Group of vlans the rule "
"applies to (if type is vlan)."))
host = models.ForeignKey('Host', related_name="rules", blank=True,
......@@ -185,15 +187,17 @@ class Vlan(models.Model):
ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True,
verbose_name=_('IPv4 address'),
help_text=_(
'The IPv4 address of the gateway. '
'Recommended value is the last valid '
'address of the subnet, for example '
'10.4.255.254 for 10.4.0.0/16.'))
'The IPv4 address of the gateway. '
'Recommended value is the last '
'valid address of the subnet, '
'for example '
'10.4.255.254 for 10.4.0.0/16.'))
ipv6 = models.GenericIPAddressField(protocol='ipv6',
unique=True,
verbose_name=_('IPv6 address'),
help_text=_(
'The IPv6 address of the gateway.'))
'The IPv6 address of the '
'gateway.'))
snat_ip = models.GenericIPAddressField(protocol='ipv4', blank=True,
null=True,
verbose_name=_('NAT IP address'),
......
from celery.task import Task, PeriodicTask
import celery
from django.core.cache import cache
import os
import time
from firewall.fw import *
import django.conf
settings = django.conf.settings.FIREWALL_SETTINGS
@celery.task
def reload_dns_task(data):
pass
@celery.task
def reload_firewall_task(data4, data6):
pass
@celery.task
def reload_dhcp_task(data):
pass
@celery.task
def reload_blacklist_task(data):
pass
class Periodic(PeriodicTask):
run_every = timedelta(seconds=10)
......@@ -48,6 +54,7 @@ class Periodic(PeriodicTask):
reload_blacklist_task.delay(list(ipset()))
print "blacklist ujratoltese kesz"
class ReloadTask(Task):
def run(self, type='Host'):
......@@ -64,4 +71,3 @@ class ReloadTask(Task):
cache.add("blacklist_lock", "true", 30)
print type
from django.test import TestCase
from admin import HostAdmin
class MockInstance:
def __init__(self, groups):
self.groups = MockGroups(groups)
class MockGroup:
def __init__(self, name):
self.name = name
class MockGroups:
def __init__(self, groups):
self.groups = groups
......@@ -16,6 +19,7 @@ class MockGroups:
def all(self):
return self.groups
class HostAdminTestCase(TestCase):
def test_no_groups(self):
instance = MockInstance([])
......@@ -29,6 +33,6 @@ class HostAdminTestCase(TestCase):
def test_multiple_groups(self):
instance = MockInstance([MockGroup("alma"),
MockGroup("korte"), MockGroup("szilva")])
MockGroup("korte"), MockGroup("szilva")])
l = HostAdmin.list_groups(instance)
self.assertEqual(l, "alma, korte, szilva")
......@@ -2,12 +2,10 @@ import base64
import datetime
import json
import re
import sys
from django.conf import settings
from django.db import IntegrityError
from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.template.loader import render_to_string
from django.utils import translation
from django.utils.timezone import utc
......@@ -15,13 +13,13 @@ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from celery.task.control import inspect
from tasks import *
from firewall.fw import *
from firewall.models import *
from one.tasks import SendMailTask
def reload_firewall(request):
if request.user.is_authenticated():
if request.user.is_superuser:
......@@ -34,34 +32,46 @@ def reload_firewall(request):
html = _("Dear anonymous, you've not signed in yet!")
return HttpResponse(html)
@csrf_exempt
@require_POST
def firewall_api(request):
try:
data=json.loads(base64.b64decode(request.POST["data"]))
data = json.loads(base64.b64decode(request.POST["data"]))
command = request.POST["command"]
if data["password"] != "bdmegintelrontottaanetet":
raise Exception(_("Wrong password."))
if command == "blacklist":
obj, created = Blacklist.objects.get_or_create(ipv4=data["ip"])
obj.reason=data["reason"]
obj.snort_message=data["snort_message"]
obj.reason = data["reason"]
obj.snort_message = data["snort_message"]
if created:
try:
obj.host = Host.objects.get(ipv4=data["ip"])
user = obj.host.owner
lang = user.person_set.all()[0].language
translation.activate(lang)
msg = render_to_string('mails/notification-ban-now.txt',
{ 'user': user,
'bl': obj,
'instance:': obj.host.instance_set.get(),
'url': settings.CLOUD_URL} )
SendMailTask.delay(to=obj.host.owner.email, subject='[IK Cloud] %s' % obj.host.instance_set.get().name, msg=msg, sender=u'cloud@ik.bme.hu')
except (Host.DoesNotExist, ValidationError, IntegrityError, AttributeError):
msg = render_to_string(
'mails/notification-ban-now.txt',
{
'user': user,
'bl': obj,
'instance:': obj.host.instance_set.get(),
'url': settings.CLOUD_URL
})
SendMailTask.delay(
to=obj.host.owner.email,
subject='[IK Cloud] %s' %
obj.host.instance_set.get().name,
msg=msg, sender=u'cloud@ik.bme.hu')
except (Host.DoesNotExist, ValidationError,
IntegrityError, AttributeError):
pass
if obj.type == 'tempwhite' and obj.modified_at + datetime.timedelta(minutes=1) < datetime.datetime.utcnow().replace(tzinfo=utc):
modified = obj.modified_at + datetime.timedelta(minutes=1)
now = datetime.dateime.utcnow().replace(tzinfo=utc)
if obj.type == 'tempwhite' and modified < now:
obj.type = 'tempban'
obj.save()
return HttpResponse(unicode(_("OK")))
......@@ -75,27 +85,26 @@ def firewall_api(request):
data["owner"] = "opennebula"
owner = auth.models.User.objects.get(username=data["owner"])
host = Host(hostname=data["hostname"],
vlan=Vlan.objects.get(name=data["vlan"]),
mac=data["mac"], ipv4=data["ip"], owner=owner,
description=data["description"], pub_ipv4=
vlan=Vlan.objects.get(name=data["vlan"]),
mac=data["mac"], ipv4=data["ip"], owner=owner,
description=data["description"], pub_ipv4=
Vlan.objects.get(name=data["vlan"]).snat_ip,
shared_ip=True)
shared_ip=True)
host.full_clean()
host.save()
host.enable_net()
for p in data["portforward"]:
host.add_port(proto=p["proto"],
public=int(p["public_port"]),
private=int(p["private_port"]))
host.add_port(proto=p["proto"], public=int(p["public_port"]),
private=int(p["private_port"]))
elif command == "destroy":
data["owner"] = "opennebula"
print data["hostname"]
owner = auth.models.User.objects.get(username=data["owner"])
host = Host.objects.get(hostname=data["hostname"],
owner=owner)
owner=owner)
host.delete()
else:
......
......@@ -9,4 +9,4 @@ setuid cloud
env DJANGO_SETTINGS_MODULE=cloud.settings.dev
env DJANGO_DB_PASSWORD=asjklddfjklqjf
env DJANGO_SECRET_KEY=asjklddfjklqjfasjklddfjklqjfasjklddfjklqjf
exec /opt/webadmin/cloud/manage.py celery worker --loglevel=info -c 1 -Q local
exec /opt/webadmin/cloud/manage.py celery worker --loglevel=info -c 1 -Q local -B
#!/bin/bash
nev=dev-$(hostname|tr -dc 0-9)
sudo sed -i /etc/hosts -e "/127.0.1.1/ s/.*/127.0.1.1 $nev.cloud.ik.bme.hu $nev/"
sudo tee /etc/hostname <<<$nev
sudo hostname $nev
sudo /etc/init.d/rabbitmq-server stop || true
sudo /etc/init.d/rabbitmq-server start
sudo pip install django_extensions
sudo pip install django-nose
sudo pip install django-debug-toolbar
......@@ -50,7 +57,13 @@ user_manager = FAKEUserManager.sh
temp_dir = /tmp/dl
EOF
for i in cloudstore toplist django
#Refresh oned config
sudo cp /opt/webadmin/cloud/miscellaneous/devenv/oned.conf /etc/one/oned.conf
sudo rm -f /opt/update_state
sudo ln -s /opt/webadmin/cloud/miscellaneous/celery/opennebula_celery.py /opt/update_state
sudo /etc/init.d/opennebula restart
for i in cloudstore toplist django celeryone celery
do
sudo cp /opt/webadmin/cloud/miscellaneous/devenv/$i.conf /etc/init/
sudo start $i
......
......@@ -64,13 +64,17 @@ class UserCloudDetails(models.Model):
help_text=_('Disk quota in mebibytes.'))
def reset_keys(self):
"""Deprecated. Use reset_ssh_keys instead."""
self.reset_ssh_keys()
def reset_ssh_keys(self):
"""Delete old SSH key pair and generate new one."""
pri, pub = keygen()
self.ssh_private_key = pri
try:
self.ssh_key.key = pub
except:
except AttributeError:
self.ssh_key = SshKey(user=self.user, key=pub)
self.ssh_key.save()
self.ssh_key_id = self.ssh_key.id
......@@ -79,40 +83,45 @@ class UserCloudDetails(models.Model):
def reset_smb(self):
"""Generate new Samba password."""
self.smb_password = pwgen()
self.save()
def get_weighted_instance_count(self):
states = ['ACTIVE', 'PENDING']
credits = [i.template.instance_type.credit
for i in self.user.instance_set.all()
if i.state in ('ACTIVE', 'PENDING', )]
for i in self.user.instance_set.filter(state__in=states)]
return sum(credits)
def get_instance_pc(self):
return 100 * self.get_weighted_instance_count() / self.instance_quota
"""Get what percent of the user's instance quota is in use."""
inst_quota = self.instance_quota
if inst_quota <= 0:
return 100
else:
return 100 * self.get_weighted_instance_count() / inst_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
credits = [s.template.instance_type.credit * s.instance_limit
for s in Share.objects.filter(owner=self.user)]
return sum(credits)
def get_share_pc(self):
assert self.share_quota > 0
return 100 * self.get_weighted_share_count() / self.share_quota
"""Get what percent of the user's share quota is in use."""
share_quota = self.share_quota
if share_quota <= 0:
return 100
else:
return 100 * self.get_weighted_share_count() / share_quota
def set_quota(sender, instance, created, **kwargs):
try:
if not StoreApi.userexist(instance.user.username):
try:
password = instance.smb_password
quota = instance.disk_quota * 1024
key_list = [key.key for key in instance.user.sshkey_set.all()]
except:
pass
password = instance.smb_password
quota = instance.disk_quota * 1024
key_list = [k.key for k in instance.user.sshkey_set.all()]
# Create user
if not StoreApi.createuser(instance.user.username, password,
key_list, quota):
pass
StoreApi.createuser(instance.user.username, password, key_list,
quota)
else:
StoreApi.set_quota(instance.user.username,
instance.disk_quota * 1024)
......@@ -124,7 +133,7 @@ post_save.connect(set_quota, sender=UserCloudDetails)
def reset_keys(sender, instance, created, **kwargs):
if created:
instance.reset_smb()
instance.reset_keys()
instance.reset_ssh_keys()
post_save.connect(reset_keys, sender=UserCloudDetails)
......@@ -139,7 +148,7 @@ class OpenSshKeyValidator(object):
def __call__(self, value):
try:
value = "%s comment" % value
value = value + ' comment'
type, key_string, comment = value.split(None, 2)
if type not in self.valid_types:
raise ValidationError(_('OpenSSH key type %s is not '
......@@ -205,10 +214,10 @@ class Share(models.Model):
'for this share.'))
per_user_limit = models.IntegerField(verbose_name=_('per user limit'),
help_text=_('Maximal count of '
'instances launchable by '
'a single user.'))
owner = models.ForeignKey(
User, null=True, blank=True, related_name='share_set')
'instances launchable '
'by a single user.'))
owner = models.ForeignKey(User, null=True, blank=True,
related_name='share_set')
class Meta:
ordering = ['group', 'template', 'owner', ]
......@@ -217,6 +226,9 @@ class Share(models.Model):
@classmethod
def extend_type(cls, t):
"""Extend the share's type descriptor with absolute deletion and
suspension time values based on the current time and intervals
already set."""
t['deletex'] = (datetime.now() + td(seconds=1) + t['delete']
if t['delete'] else None)
t['suspendx'] = (datetime.now() + td(seconds=1) + t['suspend']
......
from django.test import TestCase
from django.contrib.auth.models import User
from models import Disk, Instance, InstanceType, Network, Template, UserCloudDetails
class ViewsTestCase(TestCase):
def test_index(self):
'''Test whether index is reachable.'''
resp = self.client.get('/', follow=True)
self.assertEqual(resp.status_code, 200)
class UserCloudDetailsTestCase(TestCase):
def setUp(self):
user = User.objects.create(username="testuser",
password="testpass", email="test@mail.com",
first_name="Test", last_name="User")
disk1 = Disk.objects.create(name="testdsk1")
insttype = InstanceType.objects.create(name="testtype", CPU=4,
RAM=4096, credit=4)
ntwrk = Network.objects.create(name="testntwrk", nat=False,
public=True)
tmplt1 = Template.objects.create(name="testtmplt1", disk=disk1,
instance_type=insttype, network=ntwrk, owner=user)
tmplt2 = Template.objects.create(name="testtmplt2", disk=disk1,
instance_type=insttype, network=ntwrk, owner=user)
self.testinst1 = Instance.objects.create(owner=user, template=tmplt1,
state="ACTIVE")
self.testinst2 = Instance.objects.create(owner=user, template=tmplt2,
state="ACTIVE")
self.testdetails = UserCloudDetails.objects.get(user=user)
def test_get_weighted_instance_count(self):
credits = (self.testinst1.template.instance_type.credit +
self.testinst2.template.instance_type.credit)
self.assertEqual(credits, self.testdetails
.get_weighted_instance_count())
self.testinst1.state = "STOPPED"
self.testinst1.save()
self.assertEqual(self.testinst2.template.instance_type.credit,
self.testdetails.get_weighted_instance_count())
self.testinst2.state = "STOPPED"
self.testinst2.save()
self.assertEqual(0, self.testdetails.get_weighted_instance_count())
from datetime import datetime, timedelta
from mock import Mock, patch
from nose import with_setup
from nose.tools import raises
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.test import TestCase
from ..models import (Disk, Instance, InstanceType, Network, Share,
Template, set_quota, reset_keys, OpenSshKeyValidator)
from ..util import keygen
from school.models import Course, Group, Semester
from store.api import StoreApi
class UserCloudDetailsTestCase(TestCase):
def setUp(self):
user = User.objects.create(username="testuser", password="testpass",
email="test@mail.com", first_name="Test",
last_name="User")
disk1 = Disk.objects.create(name="testdsk1")
insttype = InstanceType.objects.create(name="testtype", CPU=4,
RAM=4096, credit=4)
ntwrk = Network.objects.create(name="testntwrk", nat=False,
public=True)
tmplt1 = Template.objects.create(name="testtmplt1", disk=disk1,
instance_type=insttype, owner=user,
network=ntwrk)
tmplt2 = Template.objects.create(name="testtmplt2", disk=disk1,
instance_type=insttype, owner=user,
network=ntwrk)
self.testinst1 = Instance.objects.create(owner=user, state="ACTIVE",
template=tmplt1)
self.testinst2 = Instance.objects.create(owner=user, state="ACTIVE",
template=tmplt2)
self.userdetails = user.cloud_details
date = datetime.now().date()
delta = timedelta(weeks=7)
sem = Semester.objects.create(name="testsem", start=date-delta,
end=date+delta)
course1 = Course.objects.create(code='tccode1', name='testcourse1',
short_name='tc1')
grp1 = Group.objects.create(name="testgroup1", semester=sem,
course=course1)
self.share1 = Share.objects.create(name="testshare1", group=grp1,
template=tmplt1, owner=user,
instance_limit=2,
per_user_limit=1)
def test_reset_keys(self):
private_key = self.userdetails.ssh_private_key
public_key = self.userdetails.ssh_key.key
self.userdetails.reset_keys()
self.assertIsNotNone(self.userdetails.ssh_private_key)
self.assertNotEqual(private_key, self.userdetails.ssh_private_key)
self.assertIsNotNone(self.userdetails.ssh_key.key)
self.assertNotEqual(public_key, self.userdetails.ssh_key.key)
def test_reset_keys_without_key(self):
private_key = self.userdetails.ssh_private_key
self.userdetails.ssh_key = None
self.userdetails.save()
self.userdetails.reset_keys()
self.assertIsNotNone(self.userdetails.ssh_private_key)
self.assertNotEqual(private_key, self.userdetails.ssh_private_key)
self.assertIsNotNone(self.userdetails.ssh_key)
def test_reset_smb(self):
smb_password = self.userdetails.smb_password
self.userdetails.reset_smb()
self.assertIsNotNone(self.userdetails.smb_password)
self.assertNotEqual(smb_password, self.userdetails.smb_password)
def test_get_weighted_instance_count(self):
credits = (self.testinst1.template.instance_type.credit +
self.testinst2.template.instance_type.credit)
self.assertEqual(credits,
self.userdetails.get_weighted_instance_count())
self.testinst1.state = "STOPPED"
self.testinst1.save()
self.assertEqual(self.testinst2.template.instance_type.credit,
self.userdetails.get_weighted_instance_count())
self.testinst2.state = "STOPPED"
self.testinst2.save()
self.assertEqual(0, self.userdetails.get_weighted_instance_count())
def test_get_instance_pc(self):
instance_pc = self.userdetails.get_instance_pc()
self.assertTrue(instance_pc >= 0)
def test_get_instance_pc_with_zero_instance_quota(self):
self.userdetails.instance_quota = 0
self.userdetails.save()
self.assertEqual(100, self.userdetails.get_instance_pc())
def test_get_weighted_share_count(self):
share = self.share1
count = share.template.instance_type.credit * share.instance_limit
self.assertEqual(count, self.userdetails.get_weighted_share_count())
def test_get_share_pc(self):
self.userdetails.share_quota = 50
share_pc = self.userdetails.get_share_pc()
self.assertTrue(share_pc >= 0)
def test_get_share_pc_with_zero_share_quota(self):
self.userdetails.share_quota = 0
self.userdetails.save()
self.assertEqual(100, self.userdetails.get_share_pc())
def create_user():
return User.objects.create(username="testuser", password="testpass",
email="test@mail.com", first_name="Test",
last_name="User")
def delete_user():
User.objects.filter(username="testuser").delete()
@with_setup(create_user, delete_user)
def test_set_quota():
user = User.objects.get(username="testuser")
details = user.cloud_details
set_quota(None, details, None)
assert StoreApi.userexist(user.username)
# TODO check quota value
@with_setup(create_user, delete_user)
@patch('one.models.StoreApi')
def test_set_quota_without_store_user(MockStoreApi):
MockStoreApi.userexist = Mock(return_value=False)
MockStoreApi.createuser = Mock()
user = User.objects.get(username="testuser")
details = user.cloud_details
set_quota(None, details, None)
MockStoreApi.userexist.assert_called_once_with(user.username)
assert MockStoreApi.createuser.called
assert MockStoreApi.createuser.call_count == 1
@with_setup(create_user, delete_user)
def test_reset_keys_when_created():
mock_details = Mock()
mock_details.reset_smb = Mock(return_value=None)
mock_details.reset_ssh_keys = Mock(return_value=None)
reset_keys(None, mock_details, True)
mock_details.reset_smb.assert_called_once_with()
mock_details.reset_ssh_keys.assert_called_once_with()
@with_setup(create_user, delete_user)
def test_reset_keys_when_not_created():
mock_details = Mock()
mock_details.reset_smb = Mock(return_value=None)
mock_details.reset_ssh_keys = Mock(return_value=None)
reset_keys(None, mock_details, False)
assert not mock_details.reset_smb.called
assert not mock_details.reset_ssh_keys.called
def test_OpenSshKeyValidator_init_with_types():
key_types = ['my-key-type']
validator = OpenSshKeyValidator(types=key_types)
assert validator.valid_types == key_types
def test_OpenSshKeyValidator_with_valid_key():
validator = OpenSshKeyValidator()
_, public_key = keygen()
validator(public_key)
@raises(ValidationError)
def test_OpenSshKeyValidator_with_empty_string_as_key():
validator = OpenSshKeyValidator()
public_key = ""
validator(public_key)
@raises(ValidationError)
def test_OpenSshKeyValidator_with_invalid_key_type():
validator = OpenSshKeyValidator()
_, public_key = keygen()
_key_type, rest = public_key.split(None, 1)
public_key = 'my-key-type ' + rest
validator(public_key)
@raises(ValidationError)
def test_OpenSshKeyValidator_with_invalid_key_data():
validator = OpenSshKeyValidator()
_, public_key = keygen()
key_parts = public_key.split(None, 2)
key_parts[1] = key_parts[1][1:]
public_key = ' '.join(key_parts)
validator(public_key)
def test_Share_extend_type():
t = {'delete': timedelta(weeks=2), 'suspend': timedelta(weeks=1)}
Share.extend_type(t)
assert 'deletex' in t
assert 'suspendx' in t
assert t['deletex'] is not None
assert t['suspendx'] is not None
def test_Share_extend_type_with_no_deletion_interval():
t = {'delete': None, 'suspend': timedelta(weeks=1)}
Share.extend_type(t)
assert 'deletex' in t
assert 'suspendx' in t
assert t['deletex'] is None
assert t['suspendx'] is not None
def test_Share_extend_type_with_no_suspension_interval():
t = {'delete': timedelta(weeks=2), 'suspend': None}
Share.extend_type(t)
assert 'deletex' in t
assert 'suspendx' in t
assert t['deletex'] is not None
assert t['suspendx'] is None
from django.core.urlresolvers import reverse
from django.test import TestCase
class ViewsTestCase(TestCase):
def test_index(self):
'''Test whether index is reachable.'''
url = reverse('one.views.index')
resp = self.client.get(url, follow=True)
self.assertEqual(resp.status_code, 200)
from django.contrib import messages
from django.core.exceptions import ValidationError
from django import contrib
from django.utils.translation import ugettext_lazy as _
from school import models
import string
class GroupInline(contrib.admin.TabularInline):
model = models.Group
extra = 3
class CourseAdmin(contrib.admin.ModelAdmin):
model = models.Course
inlines = (GroupInline, )
......@@ -16,18 +14,20 @@ class CourseAdmin(contrib.admin.ModelAdmin):
list_display = ('code', 'name', 'short_name', 'owner_list')
list_editable = ('name', 'short_name')
class GroupAdmin(contrib.admin.ModelAdmin):
model = models.Group
filter_horizontal = ('owners', 'members', )
list_display = ('name', 'course', 'semester', 'owner_list', 'member_count')
list_filter = ('semester', 'course')
class SemesterAdmin(contrib.admin.ModelAdmin):
model = models.Semester
list_display = ('id', 'name', 'start', 'end')
list_editable = ('name', 'start', 'end')
contrib.admin.site.register(models.Course, CourseAdmin)
contrib.admin.site.register(models.Semester, SemesterAdmin)
contrib.admin.site.register(models.Group, GroupAdmin)
......@@ -14,6 +14,7 @@ LANGUAGE_CHOICES = (('hu', _('Hungarian')), ('en', _('English')))
logger = logging.getLogger(__name__)
def create_user_profile(sender, instance, created, **kwargs):
"""
User creation hook.
......@@ -33,32 +34,33 @@ def create_user_profile(sender, instance, created, **kwargs):
p = Person.objects.create(code=instance.username)
except Exception as e: # pragma: no cover
logger.warning("Couldn't create profile for user: %(username)s"
"\nReason: %(exception)s",
{"username": instance.username,
"exception": e})
"\nReason: %(exception)s",
{"username": instance.username, "exception": e})
return
p.clean()
p.save()
post_save.connect(create_user_profile, sender=User)
class Person(models.Model):
"""
Personal settings and attributes of a user.
"""
user = models.ForeignKey(User, null=True, blank=True, unique=True)
language = models.CharField(verbose_name=_('language'), blank=False,
max_length=10, choices=LANGUAGE_CHOICES, default=LANGUAGE_CODE)
max_length=10, choices=LANGUAGE_CHOICES,
default=LANGUAGE_CODE)
code = models.CharField(_('code'), max_length=30, unique=True)
def get_owned_shares(self):
"""Get the shares of the groups which the person owns."""
return one.models.Share.objects.filter(
group__in=self.owned_groups.all())
group__in=self.owned_groups.all())
def get_shares(self):
"""Get the shares of the groups which the person is a member of."""
return one.models.Share.objects.filter(
group__in=self.course_groups.all())
group__in=self.course_groups.all())
def short_name(self):
if self.user:
......@@ -74,29 +76,35 @@ class Person(models.Model):
if self.user.last_name and self.user.first_name:
# TRANSLATORS: full name format used in enumerations
return _("%(first)s %(last)s") % {
'first': self.user.first_name,
'last': self.user.last_name}
'first': self.user.first_name,
'last': self.user.last_name}
else:
return self.user.username
else:
return self.code
def save(self, *args, **kwargs):
self.full_clean()
super(Person, self).save(*args, **kwargs)
class Meta:
verbose_name = _('person')
verbose_name_plural = _('persons')
class Course(models.Model):
code = models.CharField(max_length=20, unique=True,
verbose_name=_('course code'))
verbose_name=_('course code'))
name = models.CharField(max_length=80, null=True, blank=True,
verbose_name=_('name'))
verbose_name=_('name'))
short_name = models.CharField(max_length=10, null=True, blank=True,
verbose_name=_('name'))
default_group = models.ForeignKey('Group', null=True, blank=True,
related_name='default_group_of', verbose_name=_('default group'),
help_text=_('New users will be automatically assigned to this group.'))
verbose_name=_('name'))
default_group = models.ForeignKey(
'Group', null=True, blank=True, related_name='default_group_of',
verbose_name=_('default group'), help_text=_('New users will be '
'automatically assigned to this group.'))
owners = models.ManyToManyField(Person, blank=True, null=True,
verbose_name=_('owners'))
verbose_name=_('owners'))
class Meta:
verbose_name = _('course')
......@@ -107,7 +115,8 @@ class Course(models.Model):
return self.default_group
else:
default_group = Group(name=_("%s (auto)") % self.short(),
semester=Semester.get_current(), course=self)
semester=Semester.get_current(),
course=self)
default_group.save()
self.default_group_id = default_group.id
self.save()
......@@ -143,7 +152,7 @@ class Course(models.Model):
class Semester(models.Model):
name = models.CharField(max_length=20, unique=True, null=False,
verbose_name=_('name'))
verbose_name=_('name'))
start = models.DateField(verbose_name=_('start'))
end = models.DateField(verbose_name=_('end'))
......@@ -171,13 +180,15 @@ class Semester(models.Model):
class Group(models.Model):
name = models.CharField(max_length=80, verbose_name=_('name'))
course = models.ForeignKey('Course', null=True, blank=True,
verbose_name=_('course'))
verbose_name=_('course'))
semester = models.ForeignKey('Semester', null=False, blank=False,
verbose_name=_('semester'))
verbose_name=_('semester'))
owners = models.ManyToManyField(Person, blank=True, null=True,
related_name='owned_groups', verbose_name=_('owners'))
related_name='owned_groups',
verbose_name=_('owners'))
members = models.ManyToManyField(Person, blank=True, null=True,
related_name='course_groups', verbose_name=_('members'))
related_name='course_groups',
verbose_name=_('members'))
class Meta:
unique_together = (('name', 'course', 'semester', ), )
......
from datetime import datetime, timedelta
from django.test import TestCase
from django.contrib.auth.models import User, Group as AuthGroup
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from ..models import create_user_profile, Person, Course, Semester, Group
class CreateUserProfileTestCase(TestCase):
def setUp(self):
self.user = User(username="testuser", password="testpass",
email="test@mail.com", first_name="Test", last_name="User")
email="test@mail.com", first_name="Test",
last_name="User")
Person.objects.all().delete()
with self.assertRaises(Person.DoesNotExist):
Person.objects.get(code=self.user.username)
......@@ -23,6 +25,7 @@ class CreateUserProfileTestCase(TestCase):
create_user_profile(self.user.__class__, self.user, True)
self.assertIsNotNone(Person.objects.get(code=self.user.username))
class PersonTestCase(TestCase):
"""Test 'static' Person facts."""
def test_language_code_in_choices(self):
......@@ -32,11 +35,13 @@ class PersonTestCase(TestCase):
choice_codes = [code for (code, _) in language_field.choices]
self.assertIn(language_field.default, choice_codes)
class PersonWithUserTestCase(TestCase):
"""Test Person entities which have their user attribute set."""
def setUp(self):
self.user = User(username="testuser", password="testpass",
email="test@mail.com", first_name="Test", last_name="User")
email="test@mail.com", first_name="Test",
last_name="User")
Person.objects.all().delete()
self.person = Person.objects.create(code='testcode', user=self.user)
......@@ -60,6 +65,7 @@ class PersonWithUserTestCase(TestCase):
self.person.user.last_name = None
self.assertIsNotNone(self.person.__unicode__())
class PersonWithoutUserTestCase(TestCase):
"""Test Person entities which doesn't have their user attribute set."""
def setUp(self):
......@@ -78,6 +84,7 @@ class PersonWithoutUserTestCase(TestCase):
def test_unicode(self):
self.assertIsNotNone(self.person.__unicode__())
class CourseTestCase(TestCase):
def setUp(self):
now = datetime.now()
......@@ -85,10 +92,10 @@ class CourseTestCase(TestCase):
delta = timedelta(weeks=7)
self.testperson1 = Person.objects.create(code="testperson1")
self.testperson2 = Person.objects.create(code="testperson2")
self.testsemester = Semester.objects.create(name="testsemester",
start=date-delta, end=date+delta)
self.testcourse = Course.objects.create(code="testcode",
name="testname", short_name="tn")
self.testsemester = Semester.objects.create(
name="testsemester", start=date-delta, end=date+delta)
self.testcourse = Course.objects.create(
code="testcode", name="testname", short_name="tn")
self.testcourse.owners.add(self.testperson1, self.testperson2)
def test_get_or_create_default_group(self):
......@@ -118,18 +125,19 @@ class CourseTestCase(TestCase):
self.testcourse.short_name = None
self.assertIsNotNone(self.testcourse.short())
class SemesterTestCase(TestCase):
def setUp(self):
now = datetime.now()
date = now.date()
self.now = now
delta = timedelta(weeks=7)
self.last_semester = Semester.objects.create(name="testsem1",
start=date-3*delta, end=date-delta)
self.current_semester = Semester.objects.create(name="testsem2",
start=date-delta, end=date+delta)
self.next_semester = Semester.objects.create(name="testsem3",
start=date+delta, end=date+3*delta)
self.last_semester = Semester.objects.create(
name="testsem1", start=date-3*delta, end=date-delta)
self.current_semester = Semester.objects.create(
name="testsem2", start=date-delta, end=date+delta)
self.next_semester = Semester.objects.create(
name="testsem3", start=date+delta, end=date+3*delta)
def test_is_on(self):
self.assertFalse(self.last_semester.is_on(self.now))
......@@ -146,16 +154,17 @@ class SemesterTestCase(TestCase):
def test_unicode(self):
self.current_semester.__unicode__()
class GroupTestCase(TestCase):
def setUp(self):
date = datetime.now().date()
delta = timedelta(weeks=7)
semester = Semester.objects.create(name="testsem",
start=date-delta, end=date+delta)
self.testcourse = Course.objects.create(code="testcode",
name="testname", short_name="tn")
self.testgroup = Group.objects.create(name="testgrp",
semester=semester, course=self.testcourse)
semester = Semester.objects.create(
name="testsem", start=date-delta, end=date+delta)
self.testcourse = Course.objects.create(
code="testcode", name="testname", short_name="tn")
self.testgroup = Group.objects.create(
name="testgrp", semester=semester, course=self.testcourse)
def test_owner_list(self):
self.assertIsNotNone(self.testgroup.owner_list())
......@@ -184,4 +193,3 @@ class GroupTestCase(TestCase):
def test_get_absolute_url(self):
self.assertIsNotNone(self.testgroup.get_absolute_url())
from datetime import datetime
from itertools import chain
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User, Group as AGroup
from django.contrib import messages
from django.core import signing
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.mail import mail_managers, send_mail
from django.core.urlresolvers import reverse
from django.db import transaction
from django.forms import ModelForm, Textarea
from django.http import Http404
from django.shortcuts import render, render_to_response, get_object_or_404, redirect
from django.http import HttpResponse
from django.shortcuts import (render_to_response, get_object_or_404,
redirect)
from django.template import RequestContext
from django.template.loader import render_to_string
from django.utils.decorators import method_decorator
from django.utils.http import is_safe_url
from django.utils.translation import get_language as lang
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import *
from django.views.generic import *
from one.models import *
from school.models import *
from one.models import Template, UserCloudDetails
from school.models import Person, Semester, Course, Group
import django.contrib.auth as auth
import logging
import json
import re
logger = logging.getLogger(__name__)
neptun_re = re.compile('^[a-zA-Z][a-zA-Z0-9]{5}$')
def logout(request):
auth.logout(request)
return redirect('/Shibboleth.sso/Logout?return=https%3a%2f%2fcloud.ik.bme.hu%2f')
url = '/Shibboleth.sso/Logout?return=https%3a%2f%2fcloud.ik.bme.hu%2f'
return redirect(url)
@ensure_csrf_cookie
......@@ -51,7 +47,9 @@ def login(request):
try:
user.email = request.META['email']
except KeyError:
messages.error(request, _("The identity provider did not pass the mandatory e-mail data."))
messages.error(request,
_("The identity provider did not pass the mandatory "
"e-mail data."))
raise PermissionDenied()
user.save()
p, created = Person.objects.get_or_create(code=user.username)
......@@ -73,12 +71,11 @@ def login(request):
try:
g.members.add(p)
g.save()
messages.info(request,
_('Course "%s" added.') % g.course)
messages.info(request, _('Course "%s" added.') % g.course)
logger.info('Django Course "%s" added.' % g.course)
except Exception as e:
messages.error(request,
_('Failed to add course "%s".') % g.course)
_('Failed to add course "%s".') % g.course)
logger.warning("Django ex %s" % e)
held = request.META['niifEduPersonHeldCourse']
......@@ -92,10 +89,11 @@ def login(request):
co.owners.add(p)
g.owners.add(p)
messages.info(request,
_('Course "%s" ownership added.') % g.course)
_('Course "%s" ownership added.') % g.course)
except Exception as e:
messages.error(request,
_('Failed to add course "%s" ownership.') % g.course)
_('Failed to add course "%s" ownership.')
% g.course)
logger.warning("Django ex %s" % e)
co.save()
g.save()
......@@ -116,7 +114,7 @@ def login(request):
logger.info("Django affiliation group %s added to %s" % (a, p))
except Exception as e:
logger.warning("Django FAILed to add affiliation group %s to %s."
" Reason: %s" % (a, p, e))
" Reason: %s" % (a, p, e))
user.save()
p.save()
......@@ -155,7 +153,7 @@ def group_show(request, gid):
user = request.user
group = get_object_or_404(Group, id=gid)
mytemplates = Template.objects.filter(owner=request.user, state='READY')
mytemplates = Template.objects.filter(owner=user, state='READY')
for t in mytemplates:
t.myshares = t.share_set.filter(group=group)
......@@ -172,7 +170,7 @@ def group_show(request, gid):
'mytemplates': mytemplates,
'publictemplates': publictemplates,
'noshare': not has_share,
'userdetails': UserCloudDetails.objects.get(user=request.user),
'userdetails': UserCloudDetails.objects.get(user=user),
'owners': group.owners.all(),
}))
......@@ -185,7 +183,7 @@ def group_new(request):
members_list = [m for m in members_list if m != '']
members = []
for member in members_list:
if re.match('^[a-zA-Z][a-zA-Z0-9]{5}$', member.strip()) is None:
if neptun_re.match(member.strip()) is None:
messages.error(request, _('Invalid NEPTUN code found.'))
return redirect('/')
person, created = Person.objects.get_or_create(code=member)
......@@ -207,7 +205,7 @@ def group_new(request):
def group_ajax_add_new_member(request, gid):
group = get_object_or_404(Group, id=gid)
member = request.POST['neptun']
if re.match('^[a-zA-Z][a-zA-Z0-9]{5}$', member.strip()) is None:
if neptun_re.match(member.strip()) is None:
status = json.dumps({'status': 'Error'})
messages.error(request, _('Invalid NEPTUN code'))
return HttpResponse(status)
......@@ -223,7 +221,7 @@ def group_ajax_add_new_member(request, gid):
def group_ajax_remove_member(request, gid):
group = get_object_or_404(Group, id=gid)
member = request.POST['neptun']
if re.match('^[a-zA-Z][a-zA-Z0-9]{5}$', member) is None:
if neptun_re.match(member.strip()) is None:
status = json.dumps({'status': 'Error'})
messages.error(request, _('Invalid NEPTUN code'))
return HttpResponse(status)
......@@ -237,7 +235,9 @@ def group_ajax_remove_member(request, gid):
@login_required
def group_ajax_delete(request):
group = get_object_or_404(Group, id=request.POST['gid'])
# TODO should take parameter in URL using DELETE command
gid = request.POST['gid']
group = get_object_or_404(Group, id=gid)
group.delete()
return HttpResponse(json.dumps({
'status': 'OK'
......@@ -246,30 +246,27 @@ def group_ajax_delete(request):
@login_required
def group_ajax_owner_autocomplete(request):
users = (
User.objects.filter(last_name__istartswith=request.POST['q'])[:5] +
User.objects.filter(first_name__istartswith=request.POST['q'])[:5] +
User.objects.filter(username__istartswith=request.POST['q'])[:5])
results = map(lambda u: {
'name': u.get_full_name(),
'neptun': u.username}, users)
# TODO should be renamed to something like 'user_ajax_autocomplete'
query = request.POST['q']
users = chain(User.objects.filter(last_name__istartswith=query)[:5],
User.objects.filter(first_name__istartswith=query)[:5],
User.objects.filter(username__istartswith=query)[:5])
results = [{'name': user.get_full_name(),
'neptun': user.username} for user in users]
return HttpResponse(json.dumps(results, ensure_ascii=False))
@login_required
def group_ajax_add_new_owner(request, gid):
if request.user.cloud_details.share_quota == 0:
return HttpResponse({'status': 'denied'})
if request.user.cloud_details.share_quota <= 0:
return HttpResponse(json.dumps({'status': 'denied'}))
group = get_object_or_404(Group, id=gid)
member = request.POST['neptun']
if re.match('^[a-zA-Z][a-zA-Z0-9]{5}$', member.strip()) is None:
if neptun_re.match(member.strip()) is None:
status = json.dumps({'status': 'Error'})
messages.error(request, _('Invalid NEPTUN code'))
return HttpResponse(status)
person, created = Person.objects.get_or_create(code=member)
group.owners.add(person)
group.save()
return HttpResponse(json.dumps({
'status': 'OK'
}))
return HttpResponse(json.dumps({'status': 'OK'}))
from django import contrib
# from django.utils.translation import ugettext_lazy as _
from .models import Disk, DataStore
class DiskAdmin(contrib.admin.ModelAdmin):
list_display = ('name', 'datastore')
class DataStoreAdmin(contrib.admin.ModelAdmin):
list_display = ('name', 'path')
contrib.admin.site.register(Disk, DiskAdmin)
contrib.admin.site.register(DataStore, DataStoreAdmin)
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Disk'
db.create_table('storage_disk', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)),
('path', self.gf('django.db.models.fields.CharField')(unique=True, max_length=200)),
('format', self.gf('django.db.models.fields.CharField')(max_length=10)),
('size', self.gf('django.db.models.fields.IntegerField')()),
('type', self.gf('django.db.models.fields.CharField')(max_length=10)),
('base', self.gf('django.db.models.fields.related.ForeignKey')(related_name='snapshots', to=orm['storage.Disk'])),
('original_parent', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['storage.Disk'])),
))
db.send_create_signal('storage', ['Disk'])
def backwards(self, orm):
# Deleting model 'Disk'
db.delete_table('storage_disk')
models = {
'storage.disk': {
'Meta': {'ordering': "['name']", 'object_name': 'Disk'},
'base': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'snapshots'", 'to': "orm['storage.Disk']"}),
'format': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'original_parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['storage.Disk']"}),
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
'size': ('django.db.models.fields.IntegerField', [], {}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '10'})
}
}
complete_apps = ['storage']
\ No newline at end of file
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'Disk.original_parent'
db.alter_column('storage_disk', 'original_parent_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['storage.Disk'], null=True))
# Changing field 'Disk.base'
db.alter_column('storage_disk', 'base_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['storage.Disk']))
def backwards(self, orm):
# User chose to not deal with backwards NULL issues for 'Disk.original_parent'
raise RuntimeError("Cannot reverse this migration. 'Disk.original_parent' and its values cannot be restored.")
# User chose to not deal with backwards NULL issues for 'Disk.base'
raise RuntimeError("Cannot reverse this migration. 'Disk.base' and its values cannot be restored.")
models = {
'storage.disk': {
'Meta': {'ordering': "['name']", 'object_name': 'Disk'},
'base': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'snapshots'", 'null': 'True', 'to': "orm['storage.Disk']"}),
'format': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'original_parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['storage.Disk']", 'null': 'True', 'blank': 'True'}),
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
'size': ('django.db.models.fields.IntegerField', [], {}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '10'})
}
}
complete_apps = ['storage']
\ No newline at end of file
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'DataStore'
db.create_table('storage_datastore', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)),
('path', self.gf('django.db.models.fields.CharField')(unique=True, max_length=200)),
))
db.send_create_signal('storage', ['DataStore'])
# Deleting field 'Disk.path'
db.delete_column('storage_disk', 'path')
# Adding field 'Disk.datastore'
db.add_column('storage_disk', 'datastore',
self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['storage.DataStore']),
keep_default=False)
def backwards(self, orm):
# Deleting model 'DataStore'
db.delete_table('storage_datastore')
# User chose to not deal with backwards NULL issues for 'Disk.path'
raise RuntimeError("Cannot reverse this migration. 'Disk.path' and its values cannot be restored.")
# Deleting field 'Disk.datastore'
db.delete_column('storage_disk', 'datastore_id')
models = {
'storage.datastore': {
'Meta': {'ordering': "['name']", 'object_name': 'DataStore'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'})
},
'storage.disk': {
'Meta': {'ordering': "['name']", 'object_name': 'Disk'},
'base': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'snapshots'", 'null': 'True', 'to': "orm['storage.Disk']"}),
'datastore': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['storage.DataStore']"}),
'format': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'original_parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['storage.Disk']", 'null': 'True', 'blank': 'True'}),
'size': ('django.db.models.fields.IntegerField', [], {}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '10'})
}
}
complete_apps = ['storage']
\ No newline at end of file
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Disk.created'
db.add_column('storage_disk', 'created',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Disk.created'
db.delete_column('storage_disk', 'created')
models = {
'storage.datastore': {
'Meta': {'ordering': "['name']", 'object_name': 'DataStore'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'})
},
'storage.disk': {
'Meta': {'ordering': "['name']", 'object_name': 'Disk'},
'base': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'snapshots'", 'null': 'True', 'to': "orm['storage.Disk']"}),
'created': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'datastore': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['storage.DataStore']"}),
'format': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'original_parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['storage.Disk']", 'null': 'True', 'blank': 'True'}),
'size': ('django.db.models.fields.IntegerField', [], {}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '10'})
}
}
complete_apps = ['storage']
\ No newline at end of file
# coding=utf-8
import logging
import jsonpickle
import json
from django.db import models, transaction
from django.utils.translation import ugettext_lazy as _
from django.db.models.signals import post_save, post_delete
from .tasks import StorageDriver
logger = logging.getLogger(__name__)
class DataStore(models.Model):
name = models.CharField(max_length=100, unique=True,
verbose_name=_('name'))
path = models.CharField(max_length=200, unique=True,
verbose_name=_('path'))
class Meta:
ordering = ['name']
verbose_name = _('datastore')
verbose_name_plural = _('datastores')
def __unicode__(self):
return u'%s (%s)' % (self.name, self.path)
class Disk(models.Model):
"""Virtual disks automatically synchronized with OpenNebula."""
name = models.CharField(max_length=100, unique=True,
verbose_name=_('name'))
datastore = models.ForeignKey('DataStore')
format = models.CharField(max_length=10,
choices=(('qcow2', 'qcow2'), ('raw', 'raw')))
size = models.IntegerField()
type = models.CharField(max_length=10,
choices=(('snapshot', 'snapshot'),
('normal', 'normal')))
base = models.ForeignKey('Disk', related_name='snapshots',
null=True, blank=True)
original_parent = models.ForeignKey('Disk', null=True, blank=True)
created = models.BooleanField(default=False)
class Meta:
ordering = ['name']
verbose_name = _('disk')
verbose_name_plural = _('disks')
def to_json(self):
self.base_name = self.base.name if self.base else None
self.dir = self.datastore.path
return jsonpickle.encode(self, unpicklable=True)
def __unicode__(self):
return u"%s (#%d)" % (self.name, self.id)
@classmethod
def create_signal(cls, sender, instance, created, **kwargs):
if not instance.created:
StorageDriver.create_disk.delay(instance.to_json()).get()
instance.created = True
instance.save()
@classmethod
def delete_signal(cls, sender, instance, using, **kwargs):
StorageDriver.delete_disk.delay(instance.to_json()).get()
@classmethod
def update_disk(cls, disk):
name = disk['name']
modified = False
try:
base = cls.objects.get(name=disk['base_name'])
except cls.DoesNotExist:
base = None
try:
d = cls.objects.get(name=name)
except Disk.DoesNotExist:
d = Disk(name=name,
created=True,
datastore=DataStore.objects.get(path=disk['dir']),
format=disk['format'],
type=disk['type'])
modified = True
if d.size != disk['size'] or d.base != base:
d.size = disk['size']
d.base = base
modified = True
if modified:
d.full_clean()
d.save()
@classmethod
def update_disks(cls, delete=True):
"""Get and register virtual disks from OpenNebula."""
try:
json_data = StorageDriver.list_disks.delay().get(timeout=10)
disks = json.loads(json_data)
except:
return
with transaction.commit_on_success():
l = []
for disk in disks:
print disk
cls.update_disk(disk)
l.append(disk['name'])
if delete:
cls.objects.exclude(name__in=l).delete()
post_save.connect(Disk.create_signal, sender=Disk)
post_delete.connect(Disk.delete_signal, sender=Disk)
import celery
from celery.contrib.methods import task_method
import logging
logger = logging.getLogger(__name__)
class StorageDriver:
@celery.task(filter=task_method, name='storagedriver.list_disks')
def list_disks():
pass
@celery.task(filter=task_method, name='storagedriver.create_disk')
def create_disk(json_data):
pass
@celery.task(filter=task_method, name='storagedriver.delete_disk')
def delete_disk(json_data):
pass
@celery.task(filter=task_method, name='storagedriver.get_disk')
def get_disk(json_data):
pass
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)
# 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