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.conf import settings
from django.core.cache import cache from django.core.cache import cache
def process_debug(req): def process_debug(req):
return {'DEBUG': settings.DEBUG} return {'DEBUG': settings.DEBUG}
def process_stat(req): def process_stat(req):
if settings.STAT_DEBUG: if settings.STAT_DEBUG:
stat = { stat = {
...@@ -28,6 +27,7 @@ def process_stat(req): ...@@ -28,6 +27,7 @@ def process_stat(req):
'cloud_stat': stat, 'cloud_stat': stat,
} }
def process_release(req): def process_release(req):
return { return {
'release': settings.RELEASE, 'release': settings.RELEASE,
......
...@@ -274,11 +274,14 @@ DELETE_VM = True ...@@ -274,11 +274,14 @@ DELETE_VM = True
EMAIL_HOST = '152.66.243.92' # giccero ipv4 EMAIL_HOST = '152.66.243.92' # giccero ipv4
CLOUD_URL = 'https://cloud.ik.bme.hu/' CLOUD_URL = 'https://cloud.ik.bme.hu/'
try: try:
current_dir = os.getcwd()
os.chdir('/opt/webadmin/cloud/') os.chdir('/opt/webadmin/cloud/')
RELEASE = subprocess.check_output( RELEASE = subprocess.check_output(
['/usr/bin/git', 'describe', '--tags', '--abbrev=4']) ['/usr/bin/git', 'describe', '--tags', '--abbrev=4'])
except: except:
RELEASE = 'n/a' RELEASE = 'n/a'
finally:
os.chdir(current_dir)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
......
# coding=utf8 # coding=utf8
# Django development settings for cloud project. # Django development settings for cloud project.
from .base import * from .base import * # NOQA
DEBUG = True DEBUG = True
TEMPLATE_DEBUG = DEBUG TEMPLATE_DEBUG = DEBUG
...@@ -8,11 +8,11 @@ EMAIL_HOST = "localhost" ...@@ -8,11 +8,11 @@ EMAIL_HOST = "localhost"
EMAIL_PORT = 1025 EMAIL_PORT = 1025
ADMINS = ( ADMINS = (
('Ory, Mate', 'orymate@localhost'), ('Ory, Mate', 'orymate@localhost'),
) )
MANAGERS = ( MANAGERS = (
('Ory Mate', 'maat@localhost'), ('Ory Mate', 'maat@localhost'),
) )
INSTALLED_APPS += ("debug_toolbar", ) INSTALLED_APPS += ("debug_toolbar", )
MIDDLEWARE_CLASSES += ("debug_toolbar.middleware.DebugToolbarMiddleware", ) MIDDLEWARE_CLASSES += ("debug_toolbar.middleware.DebugToolbarMiddleware", )
INTERNAL_IPS = [('2001:738:2001:4031:5:253:%d:0' % i) for i in xrange(1, 100)] INTERNAL_IPS = [('2001:738:2001:4031:5:253:%d:0' % i) for i in xrange(1, 100)]
......
# coding=utf8 # coding=utf8
# Django production settings for cloud project. # Django production settings for cloud project.
from .base import * from .base import * # NOQA
DEBUG = False DEBUG = False
TEMPLATE_DEBUG = DEBUG TEMPLATE_DEBUG = DEBUG
......
...@@ -3,6 +3,7 @@ from django.conf.urls import patterns, include, url ...@@ -3,6 +3,7 @@ from django.conf.urls import patterns, include, url
from django.contrib import admin from django.contrib import admin
admin.autodiscover() admin.autodiscover()
js_info_dict = { js_info_dict = {
'packages': ('one', ), 'packages': ('one', ),
} }
......
...@@ -23,6 +23,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cloud.settings.prod") ...@@ -23,6 +23,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cloud.settings.prod")
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
_application = get_wsgi_application() _application = get_wsgi_application()
def application(environ, start_response): def application(environ, start_response):
# copy DJANGO_* wsgi-env vars to process-env # copy DJANGO_* wsgi-env vars to process-env
for i in environ.keys(): for i in environ.keys():
......
# -*- coding: utf8 -*- # -*- coding: utf8 -*-
from django.contrib import admin 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 from django import contrib
class RuleInline(contrib.admin.TabularInline): class RuleInline(contrib.admin.TabularInline):
model = Rule model = Rule
class RecordInline(contrib.admin.TabularInline): class RecordInline(contrib.admin.TabularInline):
model = Record model = Record
class HostAdmin(admin.ModelAdmin): class HostAdmin(admin.ModelAdmin):
list_display = ('hostname', 'vlan', 'ipv4', 'ipv6', 'pub_ipv4', 'mac', 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', ) ordering = ('hostname', )
list_filter = ('owner', 'vlan', 'groups') list_filter = ('owner', 'vlan', 'groups')
search_fields = ('hostname', 'description', 'ipv4', 'ipv6', 'mac') search_fields = ('hostname', 'description', 'ipv4', 'ipv6', 'mac')
...@@ -26,42 +30,46 @@ class HostAdmin(admin.ModelAdmin): ...@@ -26,42 +30,46 @@ class HostAdmin(admin.ModelAdmin):
names = [group.name for group in instance.groups.all()] names = [group.name for group in instance.groups.all()]
return u', '.join(names) return u', '.join(names)
class HostInline(contrib.admin.TabularInline): class HostInline(contrib.admin.TabularInline):
model = Host model = Host
fields = ('hostname', 'ipv4', 'ipv6', 'pub_ipv4', 'mac', 'shared_ip', fields = ('hostname', 'ipv4', 'ipv6', 'pub_ipv4', 'mac', 'shared_ip',
'owner', 'reverse') 'owner', 'reverse')
class VlanAdmin(admin.ModelAdmin): class VlanAdmin(admin.ModelAdmin):
list_display = ('vid', 'name', 'ipv4', 'net_ipv4', 'ipv6', 'net_ipv6', list_display = ('vid', 'name', 'ipv4', 'net_ipv4', 'ipv6', 'net_ipv6',
'description', 'domain', 'snat_ip', ) 'description', 'domain', 'snat_ip', )
ordering = ('vid', ) ordering = ('vid', )
inlines = (RuleInline, ) inlines = (RuleInline, )
class RuleAdmin(admin.ModelAdmin): class RuleAdmin(admin.ModelAdmin):
list_display = ('r_type', 'color_desc', 'owner', 'extra', 'direction', 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', list_filter = ('r_type', 'vlan', 'owner', 'direction', 'accept',
'proto', 'nat') 'proto', 'nat')
def color_desc(self, instance): def color_desc(self, instance):
"""Returns a colorful description of the instance.""" """Returns a colorful description of the instance."""
return (u'<span style="color: #FF0000;">[%(type)s]</span> ' return (u'<span style="color: #FF0000;">[%(type)s]</span> '
u'%(src)s<span style="color: #0000FF;"> ▸ </span>%(dst)s ' u'%(src)s<span style="color: #0000FF;"> ▸ </span>%(dst)s '
u'%(para)s %(desc)s') % { u'%(para)s %(desc)s') % {
'type': instance.r_type, 'type': instance.r_type,
'src': (instance.foreign_network.name 'src': (instance.foreign_network.name
if instance.direction == '1' else instance.r_type), if instance.direction == '1' else instance.r_type),
'dst': (instance.r_type if instance.direction == '1' 'dst': (instance.r_type if instance.direction == '1'
else instance.foreign_network.name), else instance.foreign_network.name),
'para': (u'<span style="color: #00FF00;">' + 'para': (u'<span style="color: #00FF00;">' +
(('proto=%s ' % instance.proto) (('proto=%s ' % instance.proto)
if instance.proto else '') + if instance.proto else '') +
(('sport=%s ' % instance.sport) (('sport=%s ' % instance.sport)
if instance.sport else '') + if instance.sport else '') +
(('dport=%s ' % instance.dport) (('dport=%s ' % instance.dport)
if instance.dport else '') + if instance.dport else '') +
'</span>'), '</span>'),
'desc': instance.description} 'desc': instance.description}
color_desc.allow_tags = True color_desc.allow_tags = True
@staticmethod @staticmethod
...@@ -73,7 +81,7 @@ class RuleAdmin(admin.ModelAdmin): ...@@ -73,7 +81,7 @@ class RuleAdmin(admin.ModelAdmin):
@staticmethod @staticmethod
def used_in(instance): def used_in(instance):
for field in [instance.vlan, instance.vlangroup, instance.host, for field in [instance.vlan, instance.vlangroup, instance.host,
instance.hostgroup, instance.firewall]: instance.hostgroup, instance.firewall]:
if field: if field:
return unicode(field) + ' ' + field._meta.object_name return unicode(field) + ' ' + field._meta.object_name
...@@ -81,16 +89,20 @@ class RuleAdmin(admin.ModelAdmin): ...@@ -81,16 +89,20 @@ class RuleAdmin(admin.ModelAdmin):
class AliasAdmin(admin.ModelAdmin): class AliasAdmin(admin.ModelAdmin):
list_display = ('alias', 'host') list_display = ('alias', 'host')
class GroupAdmin(admin.ModelAdmin): class GroupAdmin(admin.ModelAdmin):
list_display = ('name', 'owner', 'description') list_display = ('name', 'owner', 'description')
inlines = (RuleInline, ) inlines = (RuleInline, )
class FirewallAdmin(admin.ModelAdmin): class FirewallAdmin(admin.ModelAdmin):
inlines = (RuleInline, ) inlines = (RuleInline, )
class DomainAdmin(admin.ModelAdmin): class DomainAdmin(admin.ModelAdmin):
list_display = ('name', 'owner') list_display = ('name', 'owner')
class RecordAdmin(admin.ModelAdmin): class RecordAdmin(admin.ModelAdmin):
list_display = ('name_', 'type', 'address_', 'ttl', 'host', 'owner') list_display = ('name_', 'type', 'address_', 'ttl', 'host', 'owner')
...@@ -104,6 +116,7 @@ class RecordAdmin(admin.ModelAdmin): ...@@ -104,6 +116,7 @@ class RecordAdmin(admin.ModelAdmin):
a = instance.get_data() a = instance.get_data()
return a['name'] if a else None return a['name'] if a else None
class BlacklistAdmin(admin.ModelAdmin): class BlacklistAdmin(admin.ModelAdmin):
list_display = ('ipv4', 'reason', 'created_at', 'modified_at') list_display = ('ipv4', 'reason', 'created_at', 'modified_at')
...@@ -116,4 +129,3 @@ admin.site.register(Firewall, FirewallAdmin) ...@@ -116,4 +129,3 @@ admin.site.register(Firewall, FirewallAdmin)
admin.site.register(Domain, DomainAdmin) admin.site.register(Domain, DomainAdmin)
admin.site.register(Record, RecordAdmin) admin.site.register(Record, RecordAdmin)
admin.site.register(Blacklist, BlacklistAdmin) admin.site.register(Blacklist, BlacklistAdmin)
...@@ -6,12 +6,14 @@ from django.utils.ipv6 import is_valid_ipv6_address ...@@ -6,12 +6,14 @@ from django.utils.ipv6 import is_valid_ipv6_address
from south.modelsinspector import add_introspection_rules from south.modelsinspector import add_introspection_rules
import re import re
mac_re = re.compile(r'^([0-9a-fA-F]{2}(:|$)){6}$') mac_re = re.compile(r'^([0-9a-fA-F]{2}(:|$)){6}$')
alfanum_re = re.compile(r'^[A-Za-z0-9_-]+$') alfanum_re = re.compile(r'^[A-Za-z0-9_-]+$')
domain_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]+)$') ipv4_re = re.compile('^[0-9]+\.([0-9]+)\.([0-9]+)\.([0-9]+)$')
reverse_domain_re = re.compile(r'^(%\([abcd]\)d|[a-z0-9.-])+$') reverse_domain_re = re.compile(r'^(%\([abcd]\)d|[a-z0-9.-])+$')
class MACAddressFormField(fields.RegexField): class MACAddressFormField(fields.RegexField):
default_error_messages = { default_error_messages = {
'invalid': _(u'Enter a valid MAC address.'), 'invalid': _(u'Enter a valid MAC address.'),
...@@ -20,8 +22,10 @@ class MACAddressFormField(fields.RegexField): ...@@ -20,8 +22,10 @@ class MACAddressFormField(fields.RegexField):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(MACAddressFormField, self).__init__(mac_re, *args, **kwargs) super(MACAddressFormField, self).__init__(mac_re, *args, **kwargs)
class MACAddressField(models.Field): class MACAddressField(models.Field):
empty_strings_allowed = False empty_strings_allowed = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs['max_length'] = 17 kwargs['max_length'] = 17
super(MACAddressField, self).__init__(*args, **kwargs) super(MACAddressField, self).__init__(*args, **kwargs)
...@@ -35,58 +39,68 @@ class MACAddressField(models.Field): ...@@ -35,58 +39,68 @@ class MACAddressField(models.Field):
return super(MACAddressField, self).formfield(**defaults) return super(MACAddressField, self).formfield(**defaults)
add_introspection_rules([], ["firewall\.fields\.MACAddressField"]) add_introspection_rules([], ["firewall\.fields\.MACAddressField"])
def val_alfanum(value): def val_alfanum(value):
"""Validate whether the parameter is a valid alphanumeric value.""" """Validate whether the parameter is a valid alphanumeric value."""
if not alfanum_re.match(value): if not alfanum_re.match(value):
raise ValidationError(_(u'%s - only letters, numbers, underscores ' raise ValidationError(_(u'%s - only letters, numbers, underscores '
'and hyphens are allowed!') % value) 'and hyphens are allowed!') % value)
def is_valid_domain(value): def is_valid_domain(value):
"""Check whether the parameter is a valid domain name.""" """Check whether the parameter is a valid domain name."""
return domain_re.match(value) is not None return domain_re.match(value) is not None
def val_domain(value): def val_domain(value):
"""Validate whether the parameter is a valid domin name.""" """Validate whether the parameter is a valid domin name."""
if not is_valid_domain(value): if not is_valid_domain(value):
raise ValidationError(_(u'%s - invalid domain name') % value) raise ValidationError(_(u'%s - invalid domain name') % value)
def is_valid_reverse_domain(value): def is_valid_reverse_domain(value):
"""Check whether the parameter is a valid reverse domain name.""" """Check whether the parameter is a valid reverse domain name."""
return reverse_domain_re.match(value) is not None return reverse_domain_re.match(value) is not None
def val_reverse_domain(value): def val_reverse_domain(value):
"""Validate whether the parameter is a valid reverse domain name.""" """Validate whether the parameter is a valid reverse domain name."""
if not is_valid_reverse_domain(value): if not is_valid_reverse_domain(value):
raise ValidationError(u'%s - invalid reverse domain name' % value) raise ValidationError(u'%s - invalid reverse domain name' % value)
def is_valid_ipv4_address(value): def is_valid_ipv4_address(value):
"""Check whether the parameter is a valid IPv4 address.""" """Check whether the parameter is a valid IPv4 address."""
return ipv4_re.match(value) is not None return ipv4_re.match(value) is not None
def val_ipv4(value): def val_ipv4(value):
"""Validate whether the parameter is a valid IPv4 address.""" """Validate whether the parameter is a valid IPv4 address."""
if not is_valid_ipv4_address(value): if not is_valid_ipv4_address(value):
raise ValidationError(_(u'%s - not an IPv4 address') % value) raise ValidationError(_(u'%s - not an IPv4 address') % value)
def val_ipv6(value): def val_ipv6(value):
"""Validate whether the parameter is a valid IPv6 address.""" """Validate whether the parameter is a valid IPv6 address."""
if not is_valid_ipv6_address(value): if not is_valid_ipv6_address(value):
raise ValidationError(_(u'%s - not an IPv6 address') % value) raise ValidationError(_(u'%s - not an IPv6 address') % value)
def val_mx(value): def val_mx(value):
"""Validate whether the parameter is a valid MX address definition. """Validate whether the parameter is a valid MX address definition.
Expected form is <priority>:<hostname>. Expected form is <priority>:<hostname>.
""" """
mx = self.address.split(':', 1) mx = value.split(':', 1)
if not (len(mx) == 2 and mx[0].isdigit() and if not (len(mx) == 2 and mx[0].isdigit() and
domain_re.match(mx[1])): domain_re.match(mx[1])):
raise ValidationError(_("Bad MX address format. " raise ValidationError(_("Bad MX address format. "
"Should be: <priority>:<hostname>")) "Should be: <priority>:<hostname>"))
def ipv4_2_ipv6(ipv4): def ipv4_2_ipv6(ipv4):
"""Convert IPv4 address string to IPv6 address string.""" """Convert IPv4 address string to IPv6 address string."""
val_ipv4(ipv4) val_ipv4(ipv4)
m = ipv4_re.match(ipv4) m = ipv4_re.match(ipv4)
return ("2001:738:2001:4031:%s:%s:%s:0" % 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 ...@@ -4,7 +4,9 @@ from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.forms import ValidationError from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _ 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 from django.core.validators import MinValueValidator, MaxValueValidator
import django.conf import django.conf
from django.db.models.signals import post_save from django.db.models.signals import post_save
...@@ -85,7 +87,7 @@ class Rule(models.Model): ...@@ -85,7 +87,7 @@ class Rule(models.Model):
"(if type is vlan).")) "(if type is vlan)."))
vlangroup = models.ForeignKey('VlanGroup', related_name="rules", vlangroup = models.ForeignKey('VlanGroup', related_name="rules",
blank=True, null=True, verbose_name=_( blank=True, null=True, verbose_name=_(
"vlan group"), "vlan group"),
help_text=_("Group of vlans the rule " help_text=_("Group of vlans the rule "
"applies to (if type is vlan).")) "applies to (if type is vlan)."))
host = models.ForeignKey('Host', related_name="rules", blank=True, host = models.ForeignKey('Host', related_name="rules", blank=True,
...@@ -185,15 +187,17 @@ class Vlan(models.Model): ...@@ -185,15 +187,17 @@ class Vlan(models.Model):
ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True, ipv4 = models.GenericIPAddressField(protocol='ipv4', unique=True,
verbose_name=_('IPv4 address'), verbose_name=_('IPv4 address'),
help_text=_( help_text=_(
'The IPv4 address of the gateway. ' 'The IPv4 address of the gateway. '
'Recommended value is the last valid ' 'Recommended value is the last '
'address of the subnet, for example ' 'valid address of the subnet, '
'10.4.255.254 for 10.4.0.0/16.')) 'for example '
'10.4.255.254 for 10.4.0.0/16.'))
ipv6 = models.GenericIPAddressField(protocol='ipv6', ipv6 = models.GenericIPAddressField(protocol='ipv6',
unique=True, unique=True,
verbose_name=_('IPv6 address'), verbose_name=_('IPv6 address'),
help_text=_( help_text=_(
'The IPv6 address of the gateway.')) 'The IPv6 address of the '
'gateway.'))
snat_ip = models.GenericIPAddressField(protocol='ipv4', blank=True, snat_ip = models.GenericIPAddressField(protocol='ipv4', blank=True,
null=True, null=True,
verbose_name=_('NAT IP address'), verbose_name=_('NAT IP address'),
......
from celery.task import Task, PeriodicTask from celery.task import Task, PeriodicTask
import celery import celery
from django.core.cache import cache from django.core.cache import cache
import os
import time
from firewall.fw import * from firewall.fw import *
import django.conf import django.conf
settings = django.conf.settings.FIREWALL_SETTINGS settings = django.conf.settings.FIREWALL_SETTINGS
@celery.task @celery.task
def reload_dns_task(data): def reload_dns_task(data):
pass pass
@celery.task @celery.task
def reload_firewall_task(data4, data6): def reload_firewall_task(data4, data6):
pass pass
@celery.task @celery.task
def reload_dhcp_task(data): def reload_dhcp_task(data):
pass pass
@celery.task @celery.task
def reload_blacklist_task(data): def reload_blacklist_task(data):
pass pass
class Periodic(PeriodicTask): class Periodic(PeriodicTask):
run_every = timedelta(seconds=10) run_every = timedelta(seconds=10)
...@@ -48,6 +54,7 @@ class Periodic(PeriodicTask): ...@@ -48,6 +54,7 @@ class Periodic(PeriodicTask):
reload_blacklist_task.delay(list(ipset())) reload_blacklist_task.delay(list(ipset()))
print "blacklist ujratoltese kesz" print "blacklist ujratoltese kesz"
class ReloadTask(Task): class ReloadTask(Task):
def run(self, type='Host'): def run(self, type='Host'):
...@@ -64,4 +71,3 @@ class ReloadTask(Task): ...@@ -64,4 +71,3 @@ class ReloadTask(Task):
cache.add("blacklist_lock", "true", 30) cache.add("blacklist_lock", "true", 30)
print type print type
from django.test import TestCase from django.test import TestCase
from admin import HostAdmin from admin import HostAdmin
class MockInstance: class MockInstance:
def __init__(self, groups): def __init__(self, groups):
self.groups = MockGroups(groups) self.groups = MockGroups(groups)
class MockGroup: class MockGroup:
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
class MockGroups: class MockGroups:
def __init__(self, groups): def __init__(self, groups):
self.groups = groups self.groups = groups
...@@ -16,6 +19,7 @@ class MockGroups: ...@@ -16,6 +19,7 @@ class MockGroups:
def all(self): def all(self):
return self.groups return self.groups
class HostAdminTestCase(TestCase): class HostAdminTestCase(TestCase):
def test_no_groups(self): def test_no_groups(self):
instance = MockInstance([]) instance = MockInstance([])
...@@ -29,6 +33,6 @@ class HostAdminTestCase(TestCase): ...@@ -29,6 +33,6 @@ class HostAdminTestCase(TestCase):
def test_multiple_groups(self): def test_multiple_groups(self):
instance = MockInstance([MockGroup("alma"), instance = MockInstance([MockGroup("alma"),
MockGroup("korte"), MockGroup("szilva")]) MockGroup("korte"), MockGroup("szilva")])
l = HostAdmin.list_groups(instance) l = HostAdmin.list_groups(instance)
self.assertEqual(l, "alma, korte, szilva") self.assertEqual(l, "alma, korte, szilva")
...@@ -2,12 +2,10 @@ import base64 ...@@ -2,12 +2,10 @@ import base64
import datetime import datetime
import json import json
import re import re
import sys
from django.conf import settings from django.conf import settings
from django.db import IntegrityError from django.db import IntegrityError
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import render_to_response
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import translation from django.utils import translation
from django.utils.timezone import utc from django.utils.timezone import utc
...@@ -15,13 +13,13 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -15,13 +13,13 @@ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from celery.task.control import inspect
from tasks import * from tasks import *
from firewall.fw import * from firewall.fw import *
from firewall.models import * from firewall.models import *
from one.tasks import SendMailTask from one.tasks import SendMailTask
def reload_firewall(request): def reload_firewall(request):
if request.user.is_authenticated(): if request.user.is_authenticated():
if request.user.is_superuser: if request.user.is_superuser:
...@@ -34,34 +32,46 @@ def reload_firewall(request): ...@@ -34,34 +32,46 @@ def reload_firewall(request):
html = _("Dear anonymous, you've not signed in yet!") html = _("Dear anonymous, you've not signed in yet!")
return HttpResponse(html) return HttpResponse(html)
@csrf_exempt @csrf_exempt
@require_POST @require_POST
def firewall_api(request): def firewall_api(request):
try: try:
data=json.loads(base64.b64decode(request.POST["data"])) data = json.loads(base64.b64decode(request.POST["data"]))
command = request.POST["command"] command = request.POST["command"]
if data["password"] != "bdmegintelrontottaanetet": if data["password"] != "bdmegintelrontottaanetet":
raise Exception(_("Wrong password.")) raise Exception(_("Wrong password."))
if command == "blacklist": if command == "blacklist":
obj, created = Blacklist.objects.get_or_create(ipv4=data["ip"]) obj, created = Blacklist.objects.get_or_create(ipv4=data["ip"])
obj.reason=data["reason"] obj.reason = data["reason"]
obj.snort_message=data["snort_message"] obj.snort_message = data["snort_message"]
if created: if created:
try: try:
obj.host = Host.objects.get(ipv4=data["ip"]) obj.host = Host.objects.get(ipv4=data["ip"])
user = obj.host.owner user = obj.host.owner
lang = user.person_set.all()[0].language lang = user.person_set.all()[0].language
translation.activate(lang) translation.activate(lang)
msg = render_to_string('mails/notification-ban-now.txt', msg = render_to_string(
{ 'user': user, 'mails/notification-ban-now.txt',
'bl': obj, {
'instance:': obj.host.instance_set.get(), 'user': user,
'url': settings.CLOUD_URL} ) 'bl': obj,
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') 'instance:': obj.host.instance_set.get(),
except (Host.DoesNotExist, ValidationError, IntegrityError, AttributeError): '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 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.type = 'tempban'
obj.save() obj.save()
return HttpResponse(unicode(_("OK"))) return HttpResponse(unicode(_("OK")))
...@@ -75,27 +85,26 @@ def firewall_api(request): ...@@ -75,27 +85,26 @@ def firewall_api(request):
data["owner"] = "opennebula" data["owner"] = "opennebula"
owner = auth.models.User.objects.get(username=data["owner"]) owner = auth.models.User.objects.get(username=data["owner"])
host = Host(hostname=data["hostname"], host = Host(hostname=data["hostname"],
vlan=Vlan.objects.get(name=data["vlan"]), vlan=Vlan.objects.get(name=data["vlan"]),
mac=data["mac"], ipv4=data["ip"], owner=owner, mac=data["mac"], ipv4=data["ip"], owner=owner,
description=data["description"], pub_ipv4= description=data["description"], pub_ipv4=
Vlan.objects.get(name=data["vlan"]).snat_ip, Vlan.objects.get(name=data["vlan"]).snat_ip,
shared_ip=True) shared_ip=True)
host.full_clean() host.full_clean()
host.save() host.save()
host.enable_net() host.enable_net()
for p in data["portforward"]: for p in data["portforward"]:
host.add_port(proto=p["proto"], host.add_port(proto=p["proto"], public=int(p["public_port"]),
public=int(p["public_port"]), private=int(p["private_port"]))
private=int(p["private_port"]))
elif command == "destroy": elif command == "destroy":
data["owner"] = "opennebula" data["owner"] = "opennebula"
print data["hostname"] print data["hostname"]
owner = auth.models.User.objects.get(username=data["owner"]) owner = auth.models.User.objects.get(username=data["owner"])
host = Host.objects.get(hostname=data["hostname"], host = Host.objects.get(hostname=data["hostname"],
owner=owner) owner=owner)
host.delete() host.delete()
else: else:
......
...@@ -9,4 +9,4 @@ setuid cloud ...@@ -9,4 +9,4 @@ setuid cloud
env DJANGO_SETTINGS_MODULE=cloud.settings.dev env DJANGO_SETTINGS_MODULE=cloud.settings.dev
env DJANGO_DB_PASSWORD=asjklddfjklqjf env DJANGO_DB_PASSWORD=asjklddfjklqjf
env DJANGO_SECRET_KEY=asjklddfjklqjfasjklddfjklqjfasjklddfjklqjf 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 #!/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_extensions
sudo pip install django-nose sudo pip install django-nose
sudo pip install django-debug-toolbar sudo pip install django-debug-toolbar
...@@ -50,7 +57,13 @@ user_manager = FAKEUserManager.sh ...@@ -50,7 +57,13 @@ user_manager = FAKEUserManager.sh
temp_dir = /tmp/dl temp_dir = /tmp/dl
EOF 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 do
sudo cp /opt/webadmin/cloud/miscellaneous/devenv/$i.conf /etc/init/ sudo cp /opt/webadmin/cloud/miscellaneous/devenv/$i.conf /etc/init/
sudo start $i sudo start $i
......
...@@ -64,13 +64,17 @@ class UserCloudDetails(models.Model): ...@@ -64,13 +64,17 @@ class UserCloudDetails(models.Model):
help_text=_('Disk quota in mebibytes.')) help_text=_('Disk quota in mebibytes.'))
def reset_keys(self): 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.""" """Delete old SSH key pair and generate new one."""
pri, pub = keygen() pri, pub = keygen()
self.ssh_private_key = pri self.ssh_private_key = pri
try: try:
self.ssh_key.key = pub self.ssh_key.key = pub
except: except AttributeError:
self.ssh_key = SshKey(user=self.user, key=pub) self.ssh_key = SshKey(user=self.user, key=pub)
self.ssh_key.save() self.ssh_key.save()
self.ssh_key_id = self.ssh_key.id self.ssh_key_id = self.ssh_key.id
...@@ -79,40 +83,45 @@ class UserCloudDetails(models.Model): ...@@ -79,40 +83,45 @@ class UserCloudDetails(models.Model):
def reset_smb(self): def reset_smb(self):
"""Generate new Samba password.""" """Generate new Samba password."""
self.smb_password = pwgen() self.smb_password = pwgen()
self.save()
def get_weighted_instance_count(self): def get_weighted_instance_count(self):
states = ['ACTIVE', 'PENDING']
credits = [i.template.instance_type.credit credits = [i.template.instance_type.credit
for i in self.user.instance_set.all() for i in self.user.instance_set.filter(state__in=states)]
if i.state in ('ACTIVE', 'PENDING', )]
return sum(credits) return sum(credits)
def get_instance_pc(self): 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): def get_weighted_share_count(self):
c = 0 credits = [s.template.instance_type.credit * s.instance_limit
for i in Share.objects.filter(owner=self.user).all(): for s in Share.objects.filter(owner=self.user)]
c = c + i.template.instance_type.credit * i.instance_limit return sum(credits)
return c
def get_share_pc(self): def get_share_pc(self):
assert self.share_quota > 0 """Get what percent of the user's share quota is in use."""
return 100 * self.get_weighted_share_count() / self.share_quota 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): def set_quota(sender, instance, created, **kwargs):
try: try:
if not StoreApi.userexist(instance.user.username): if not StoreApi.userexist(instance.user.username):
try: password = instance.smb_password
password = instance.smb_password quota = instance.disk_quota * 1024
quota = instance.disk_quota * 1024 key_list = [k.key for k in instance.user.sshkey_set.all()]
key_list = [key.key for key in instance.user.sshkey_set.all()]
except:
pass
# Create user # Create user
if not StoreApi.createuser(instance.user.username, password, StoreApi.createuser(instance.user.username, password, key_list,
key_list, quota): quota)
pass
else: else:
StoreApi.set_quota(instance.user.username, StoreApi.set_quota(instance.user.username,
instance.disk_quota * 1024) instance.disk_quota * 1024)
...@@ -124,7 +133,7 @@ post_save.connect(set_quota, sender=UserCloudDetails) ...@@ -124,7 +133,7 @@ post_save.connect(set_quota, sender=UserCloudDetails)
def reset_keys(sender, instance, created, **kwargs): def reset_keys(sender, instance, created, **kwargs):
if created: if created:
instance.reset_smb() instance.reset_smb()
instance.reset_keys() instance.reset_ssh_keys()
post_save.connect(reset_keys, sender=UserCloudDetails) post_save.connect(reset_keys, sender=UserCloudDetails)
...@@ -139,7 +148,7 @@ class OpenSshKeyValidator(object): ...@@ -139,7 +148,7 @@ class OpenSshKeyValidator(object):
def __call__(self, value): def __call__(self, value):
try: try:
value = "%s comment" % value value = value + ' comment'
type, key_string, comment = value.split(None, 2) type, key_string, comment = value.split(None, 2)
if type not in self.valid_types: if type not in self.valid_types:
raise ValidationError(_('OpenSSH key type %s is not ' raise ValidationError(_('OpenSSH key type %s is not '
...@@ -205,10 +214,10 @@ class Share(models.Model): ...@@ -205,10 +214,10 @@ class Share(models.Model):
'for this share.')) 'for this share.'))
per_user_limit = models.IntegerField(verbose_name=_('per user limit'), per_user_limit = models.IntegerField(verbose_name=_('per user limit'),
help_text=_('Maximal count of ' help_text=_('Maximal count of '
'instances launchable by ' 'instances launchable '
'a single user.')) 'by a single user.'))
owner = models.ForeignKey( owner = models.ForeignKey(User, null=True, blank=True,
User, null=True, blank=True, related_name='share_set') related_name='share_set')
class Meta: class Meta:
ordering = ['group', 'template', 'owner', ] ordering = ['group', 'template', 'owner', ]
...@@ -217,6 +226,9 @@ class Share(models.Model): ...@@ -217,6 +226,9 @@ class Share(models.Model):
@classmethod @classmethod
def extend_type(cls, t): 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'] t['deletex'] = (datetime.now() + td(seconds=1) + t['delete']
if t['delete'] else None) if t['delete'] else None)
t['suspendx'] = (datetime.now() + td(seconds=1) + t['suspend'] 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 import contrib
from django.utils.translation import ugettext_lazy as _
from school import models from school import models
import string
class GroupInline(contrib.admin.TabularInline): class GroupInline(contrib.admin.TabularInline):
model = models.Group model = models.Group
extra = 3 extra = 3
class CourseAdmin(contrib.admin.ModelAdmin): class CourseAdmin(contrib.admin.ModelAdmin):
model = models.Course model = models.Course
inlines = (GroupInline, ) inlines = (GroupInline, )
...@@ -16,18 +14,20 @@ class CourseAdmin(contrib.admin.ModelAdmin): ...@@ -16,18 +14,20 @@ class CourseAdmin(contrib.admin.ModelAdmin):
list_display = ('code', 'name', 'short_name', 'owner_list') list_display = ('code', 'name', 'short_name', 'owner_list')
list_editable = ('name', 'short_name') list_editable = ('name', 'short_name')
class GroupAdmin(contrib.admin.ModelAdmin): class GroupAdmin(contrib.admin.ModelAdmin):
model = models.Group model = models.Group
filter_horizontal = ('owners', 'members', ) filter_horizontal = ('owners', 'members', )
list_display = ('name', 'course', 'semester', 'owner_list', 'member_count') list_display = ('name', 'course', 'semester', 'owner_list', 'member_count')
list_filter = ('semester', 'course') list_filter = ('semester', 'course')
class SemesterAdmin(contrib.admin.ModelAdmin): class SemesterAdmin(contrib.admin.ModelAdmin):
model = models.Semester model = models.Semester
list_display = ('id', 'name', 'start', 'end') list_display = ('id', 'name', 'start', 'end')
list_editable = ('name', 'start', 'end') list_editable = ('name', 'start', 'end')
contrib.admin.site.register(models.Course, CourseAdmin) contrib.admin.site.register(models.Course, CourseAdmin)
contrib.admin.site.register(models.Semester, SemesterAdmin) contrib.admin.site.register(models.Semester, SemesterAdmin)
contrib.admin.site.register(models.Group, GroupAdmin) contrib.admin.site.register(models.Group, GroupAdmin)
...@@ -14,6 +14,7 @@ LANGUAGE_CHOICES = (('hu', _('Hungarian')), ('en', _('English'))) ...@@ -14,6 +14,7 @@ LANGUAGE_CHOICES = (('hu', _('Hungarian')), ('en', _('English')))
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def create_user_profile(sender, instance, created, **kwargs): def create_user_profile(sender, instance, created, **kwargs):
""" """
User creation hook. User creation hook.
...@@ -33,32 +34,33 @@ def create_user_profile(sender, instance, created, **kwargs): ...@@ -33,32 +34,33 @@ def create_user_profile(sender, instance, created, **kwargs):
p = Person.objects.create(code=instance.username) p = Person.objects.create(code=instance.username)
except Exception as e: # pragma: no cover except Exception as e: # pragma: no cover
logger.warning("Couldn't create profile for user: %(username)s" logger.warning("Couldn't create profile for user: %(username)s"
"\nReason: %(exception)s", "\nReason: %(exception)s",
{"username": instance.username, {"username": instance.username, "exception": e})
"exception": e})
return return
p.clean() p.clean()
p.save() p.save()
post_save.connect(create_user_profile, sender=User) post_save.connect(create_user_profile, sender=User)
class Person(models.Model): class Person(models.Model):
""" """
Personal settings and attributes of a user. Personal settings and attributes of a user.
""" """
user = models.ForeignKey(User, null=True, blank=True, unique=True) user = models.ForeignKey(User, null=True, blank=True, unique=True)
language = models.CharField(verbose_name=_('language'), blank=False, 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) code = models.CharField(_('code'), max_length=30, unique=True)
def get_owned_shares(self): def get_owned_shares(self):
"""Get the shares of the groups which the person owns.""" """Get the shares of the groups which the person owns."""
return one.models.Share.objects.filter( return one.models.Share.objects.filter(
group__in=self.owned_groups.all()) group__in=self.owned_groups.all())
def get_shares(self): def get_shares(self):
"""Get the shares of the groups which the person is a member of.""" """Get the shares of the groups which the person is a member of."""
return one.models.Share.objects.filter( return one.models.Share.objects.filter(
group__in=self.course_groups.all()) group__in=self.course_groups.all())
def short_name(self): def short_name(self):
if self.user: if self.user:
...@@ -74,29 +76,35 @@ class Person(models.Model): ...@@ -74,29 +76,35 @@ class Person(models.Model):
if self.user.last_name and self.user.first_name: if self.user.last_name and self.user.first_name:
# TRANSLATORS: full name format used in enumerations # TRANSLATORS: full name format used in enumerations
return _("%(first)s %(last)s") % { return _("%(first)s %(last)s") % {
'first': self.user.first_name, 'first': self.user.first_name,
'last': self.user.last_name} 'last': self.user.last_name}
else: else:
return self.user.username return self.user.username
else: else:
return self.code return self.code
def save(self, *args, **kwargs):
self.full_clean()
super(Person, self).save(*args, **kwargs)
class Meta: class Meta:
verbose_name = _('person') verbose_name = _('person')
verbose_name_plural = _('persons') verbose_name_plural = _('persons')
class Course(models.Model): class Course(models.Model):
code = models.CharField(max_length=20, unique=True, 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, 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, short_name = models.CharField(max_length=10, null=True, blank=True,
verbose_name=_('name')) verbose_name=_('name'))
default_group = models.ForeignKey('Group', null=True, blank=True, default_group = models.ForeignKey(
related_name='default_group_of', verbose_name=_('default group'), 'Group', null=True, blank=True, related_name='default_group_of',
help_text=_('New users will be automatically assigned to this group.')) verbose_name=_('default group'), help_text=_('New users will be '
'automatically assigned to this group.'))
owners = models.ManyToManyField(Person, blank=True, null=True, owners = models.ManyToManyField(Person, blank=True, null=True,
verbose_name=_('owners')) verbose_name=_('owners'))
class Meta: class Meta:
verbose_name = _('course') verbose_name = _('course')
...@@ -107,7 +115,8 @@ class Course(models.Model): ...@@ -107,7 +115,8 @@ class Course(models.Model):
return self.default_group return self.default_group
else: else:
default_group = Group(name=_("%s (auto)") % self.short(), default_group = Group(name=_("%s (auto)") % self.short(),
semester=Semester.get_current(), course=self) semester=Semester.get_current(),
course=self)
default_group.save() default_group.save()
self.default_group_id = default_group.id self.default_group_id = default_group.id
self.save() self.save()
...@@ -143,7 +152,7 @@ class Course(models.Model): ...@@ -143,7 +152,7 @@ class Course(models.Model):
class Semester(models.Model): class Semester(models.Model):
name = models.CharField(max_length=20, unique=True, null=False, name = models.CharField(max_length=20, unique=True, null=False,
verbose_name=_('name')) verbose_name=_('name'))
start = models.DateField(verbose_name=_('start')) start = models.DateField(verbose_name=_('start'))
end = models.DateField(verbose_name=_('end')) end = models.DateField(verbose_name=_('end'))
...@@ -171,13 +180,15 @@ class Semester(models.Model): ...@@ -171,13 +180,15 @@ class Semester(models.Model):
class Group(models.Model): class Group(models.Model):
name = models.CharField(max_length=80, verbose_name=_('name')) name = models.CharField(max_length=80, verbose_name=_('name'))
course = models.ForeignKey('Course', null=True, blank=True, course = models.ForeignKey('Course', null=True, blank=True,
verbose_name=_('course')) verbose_name=_('course'))
semester = models.ForeignKey('Semester', null=False, blank=False, semester = models.ForeignKey('Semester', null=False, blank=False,
verbose_name=_('semester')) verbose_name=_('semester'))
owners = models.ManyToManyField(Person, blank=True, null=True, 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, members = models.ManyToManyField(Person, blank=True, null=True,
related_name='course_groups', verbose_name=_('members')) related_name='course_groups',
verbose_name=_('members'))
class Meta: class Meta:
unique_together = (('name', 'course', 'semester', ), ) unique_together = (('name', 'course', 'semester', ), )
......
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.test import TestCase 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 django.core.exceptions import ValidationError
from ..models import create_user_profile, Person, Course, Semester, Group from ..models import create_user_profile, Person, Course, Semester, Group
class CreateUserProfileTestCase(TestCase): class CreateUserProfileTestCase(TestCase):
def setUp(self): def setUp(self):
self.user = User(username="testuser", password="testpass", 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() Person.objects.all().delete()
with self.assertRaises(Person.DoesNotExist): with self.assertRaises(Person.DoesNotExist):
Person.objects.get(code=self.user.username) Person.objects.get(code=self.user.username)
...@@ -23,6 +25,7 @@ class CreateUserProfileTestCase(TestCase): ...@@ -23,6 +25,7 @@ class CreateUserProfileTestCase(TestCase):
create_user_profile(self.user.__class__, self.user, True) create_user_profile(self.user.__class__, self.user, True)
self.assertIsNotNone(Person.objects.get(code=self.user.username)) self.assertIsNotNone(Person.objects.get(code=self.user.username))
class PersonTestCase(TestCase): class PersonTestCase(TestCase):
"""Test 'static' Person facts.""" """Test 'static' Person facts."""
def test_language_code_in_choices(self): def test_language_code_in_choices(self):
...@@ -32,11 +35,13 @@ class PersonTestCase(TestCase): ...@@ -32,11 +35,13 @@ class PersonTestCase(TestCase):
choice_codes = [code for (code, _) in language_field.choices] choice_codes = [code for (code, _) in language_field.choices]
self.assertIn(language_field.default, choice_codes) self.assertIn(language_field.default, choice_codes)
class PersonWithUserTestCase(TestCase): class PersonWithUserTestCase(TestCase):
"""Test Person entities which have their user attribute set.""" """Test Person entities which have their user attribute set."""
def setUp(self): def setUp(self):
self.user = User(username="testuser", password="testpass", 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() Person.objects.all().delete()
self.person = Person.objects.create(code='testcode', user=self.user) self.person = Person.objects.create(code='testcode', user=self.user)
...@@ -60,6 +65,7 @@ class PersonWithUserTestCase(TestCase): ...@@ -60,6 +65,7 @@ class PersonWithUserTestCase(TestCase):
self.person.user.last_name = None self.person.user.last_name = None
self.assertIsNotNone(self.person.__unicode__()) self.assertIsNotNone(self.person.__unicode__())
class PersonWithoutUserTestCase(TestCase): class PersonWithoutUserTestCase(TestCase):
"""Test Person entities which doesn't have their user attribute set.""" """Test Person entities which doesn't have their user attribute set."""
def setUp(self): def setUp(self):
...@@ -78,6 +84,7 @@ class PersonWithoutUserTestCase(TestCase): ...@@ -78,6 +84,7 @@ class PersonWithoutUserTestCase(TestCase):
def test_unicode(self): def test_unicode(self):
self.assertIsNotNone(self.person.__unicode__()) self.assertIsNotNone(self.person.__unicode__())
class CourseTestCase(TestCase): class CourseTestCase(TestCase):
def setUp(self): def setUp(self):
now = datetime.now() now = datetime.now()
...@@ -85,10 +92,10 @@ class CourseTestCase(TestCase): ...@@ -85,10 +92,10 @@ class CourseTestCase(TestCase):
delta = timedelta(weeks=7) delta = timedelta(weeks=7)
self.testperson1 = Person.objects.create(code="testperson1") self.testperson1 = Person.objects.create(code="testperson1")
self.testperson2 = Person.objects.create(code="testperson2") self.testperson2 = Person.objects.create(code="testperson2")
self.testsemester = Semester.objects.create(name="testsemester", self.testsemester = Semester.objects.create(
start=date-delta, end=date+delta) name="testsemester", start=date-delta, end=date+delta)
self.testcourse = Course.objects.create(code="testcode", self.testcourse = Course.objects.create(
name="testname", short_name="tn") code="testcode", name="testname", short_name="tn")
self.testcourse.owners.add(self.testperson1, self.testperson2) self.testcourse.owners.add(self.testperson1, self.testperson2)
def test_get_or_create_default_group(self): def test_get_or_create_default_group(self):
...@@ -118,18 +125,19 @@ class CourseTestCase(TestCase): ...@@ -118,18 +125,19 @@ class CourseTestCase(TestCase):
self.testcourse.short_name = None self.testcourse.short_name = None
self.assertIsNotNone(self.testcourse.short()) self.assertIsNotNone(self.testcourse.short())
class SemesterTestCase(TestCase): class SemesterTestCase(TestCase):
def setUp(self): def setUp(self):
now = datetime.now() now = datetime.now()
date = now.date() date = now.date()
self.now = now self.now = now
delta = timedelta(weeks=7) delta = timedelta(weeks=7)
self.last_semester = Semester.objects.create(name="testsem1", self.last_semester = Semester.objects.create(
start=date-3*delta, end=date-delta) name="testsem1", start=date-3*delta, end=date-delta)
self.current_semester = Semester.objects.create(name="testsem2", self.current_semester = Semester.objects.create(
start=date-delta, end=date+delta) name="testsem2", start=date-delta, end=date+delta)
self.next_semester = Semester.objects.create(name="testsem3", self.next_semester = Semester.objects.create(
start=date+delta, end=date+3*delta) name="testsem3", start=date+delta, end=date+3*delta)
def test_is_on(self): def test_is_on(self):
self.assertFalse(self.last_semester.is_on(self.now)) self.assertFalse(self.last_semester.is_on(self.now))
...@@ -146,16 +154,17 @@ class SemesterTestCase(TestCase): ...@@ -146,16 +154,17 @@ class SemesterTestCase(TestCase):
def test_unicode(self): def test_unicode(self):
self.current_semester.__unicode__() self.current_semester.__unicode__()
class GroupTestCase(TestCase): class GroupTestCase(TestCase):
def setUp(self): def setUp(self):
date = datetime.now().date() date = datetime.now().date()
delta = timedelta(weeks=7) delta = timedelta(weeks=7)
semester = Semester.objects.create(name="testsem", semester = Semester.objects.create(
start=date-delta, end=date+delta) name="testsem", start=date-delta, end=date+delta)
self.testcourse = Course.objects.create(code="testcode", self.testcourse = Course.objects.create(
name="testname", short_name="tn") code="testcode", name="testname", short_name="tn")
self.testgroup = Group.objects.create(name="testgrp", self.testgroup = Group.objects.create(
semester=semester, course=self.testcourse) name="testgrp", semester=semester, course=self.testcourse)
def test_owner_list(self): def test_owner_list(self):
self.assertIsNotNone(self.testgroup.owner_list()) self.assertIsNotNone(self.testgroup.owner_list())
...@@ -184,4 +193,3 @@ class GroupTestCase(TestCase): ...@@ -184,4 +193,3 @@ class GroupTestCase(TestCase):
def test_get_absolute_url(self): def test_get_absolute_url(self):
self.assertIsNotNone(self.testgroup.get_absolute_url()) self.assertIsNotNone(self.testgroup.get_absolute_url())
from datetime import datetime
from itertools import chain from itertools import chain
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User, Group as AGroup from django.contrib.auth.models import User, Group as AGroup
from django.contrib import messages from django.contrib import messages
from django.core import signing
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.core.mail import mail_managers, send_mail
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import transaction from django.http import HttpResponse
from django.forms import ModelForm, Textarea from django.shortcuts import (render_to_response, get_object_or_404,
from django.http import Http404 redirect)
from django.shortcuts import render, render_to_response, get_object_or_404, redirect
from django.template import RequestContext 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.http import is_safe_url
from django.utils.translation import get_language as lang
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import * from one.models import Template, UserCloudDetails
from django.views.generic import * from school.models import Person, Semester, Course, Group
from one.models import *
from school.models import *
import django.contrib.auth as auth import django.contrib.auth as auth
import logging import logging
import json import json
import re import re
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
neptun_re = re.compile('^[a-zA-Z][a-zA-Z0-9]{5}$')
def logout(request): def logout(request):
auth.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 @ensure_csrf_cookie
...@@ -51,7 +47,9 @@ def login(request): ...@@ -51,7 +47,9 @@ def login(request):
try: try:
user.email = request.META['email'] user.email = request.META['email']
except KeyError: 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() raise PermissionDenied()
user.save() user.save()
p, created = Person.objects.get_or_create(code=user.username) p, created = Person.objects.get_or_create(code=user.username)
...@@ -73,12 +71,11 @@ def login(request): ...@@ -73,12 +71,11 @@ def login(request):
try: try:
g.members.add(p) g.members.add(p)
g.save() g.save()
messages.info(request, messages.info(request, _('Course "%s" added.') % g.course)
_('Course "%s" added.') % g.course)
logger.info('Django Course "%s" added.' % g.course) logger.info('Django Course "%s" added.' % g.course)
except Exception as e: except Exception as e:
messages.error(request, messages.error(request,
_('Failed to add course "%s".') % g.course) _('Failed to add course "%s".') % g.course)
logger.warning("Django ex %s" % e) logger.warning("Django ex %s" % e)
held = request.META['niifEduPersonHeldCourse'] held = request.META['niifEduPersonHeldCourse']
...@@ -92,10 +89,11 @@ def login(request): ...@@ -92,10 +89,11 @@ def login(request):
co.owners.add(p) co.owners.add(p)
g.owners.add(p) g.owners.add(p)
messages.info(request, messages.info(request,
_('Course "%s" ownership added.') % g.course) _('Course "%s" ownership added.') % g.course)
except Exception as e: except Exception as e:
messages.error(request, 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) logger.warning("Django ex %s" % e)
co.save() co.save()
g.save() g.save()
...@@ -116,7 +114,7 @@ def login(request): ...@@ -116,7 +114,7 @@ def login(request):
logger.info("Django affiliation group %s added to %s" % (a, p)) logger.info("Django affiliation group %s added to %s" % (a, p))
except Exception as e: except Exception as e:
logger.warning("Django FAILed to add affiliation group %s to %s." logger.warning("Django FAILed to add affiliation group %s to %s."
" Reason: %s" % (a, p, e)) " Reason: %s" % (a, p, e))
user.save() user.save()
p.save() p.save()
...@@ -155,7 +153,7 @@ def group_show(request, gid): ...@@ -155,7 +153,7 @@ def group_show(request, gid):
user = request.user user = request.user
group = get_object_or_404(Group, id=gid) 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: for t in mytemplates:
t.myshares = t.share_set.filter(group=group) t.myshares = t.share_set.filter(group=group)
...@@ -172,7 +170,7 @@ def group_show(request, gid): ...@@ -172,7 +170,7 @@ def group_show(request, gid):
'mytemplates': mytemplates, 'mytemplates': mytemplates,
'publictemplates': publictemplates, 'publictemplates': publictemplates,
'noshare': not has_share, 'noshare': not has_share,
'userdetails': UserCloudDetails.objects.get(user=request.user), 'userdetails': UserCloudDetails.objects.get(user=user),
'owners': group.owners.all(), 'owners': group.owners.all(),
})) }))
...@@ -185,7 +183,7 @@ def group_new(request): ...@@ -185,7 +183,7 @@ def group_new(request):
members_list = [m for m in members_list if m != ''] members_list = [m for m in members_list if m != '']
members = [] members = []
for member in members_list: 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.')) messages.error(request, _('Invalid NEPTUN code found.'))
return redirect('/') return redirect('/')
person, created = Person.objects.get_or_create(code=member) person, created = Person.objects.get_or_create(code=member)
...@@ -207,7 +205,7 @@ def group_new(request): ...@@ -207,7 +205,7 @@ def group_new(request):
def group_ajax_add_new_member(request, gid): def group_ajax_add_new_member(request, gid):
group = get_object_or_404(Group, id=gid) group = get_object_or_404(Group, id=gid)
member = request.POST['neptun'] 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'}) status = json.dumps({'status': 'Error'})
messages.error(request, _('Invalid NEPTUN code')) messages.error(request, _('Invalid NEPTUN code'))
return HttpResponse(status) return HttpResponse(status)
...@@ -223,7 +221,7 @@ def group_ajax_add_new_member(request, gid): ...@@ -223,7 +221,7 @@ def group_ajax_add_new_member(request, gid):
def group_ajax_remove_member(request, gid): def group_ajax_remove_member(request, gid):
group = get_object_or_404(Group, id=gid) group = get_object_or_404(Group, id=gid)
member = request.POST['neptun'] 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'}) status = json.dumps({'status': 'Error'})
messages.error(request, _('Invalid NEPTUN code')) messages.error(request, _('Invalid NEPTUN code'))
return HttpResponse(status) return HttpResponse(status)
...@@ -237,7 +235,9 @@ def group_ajax_remove_member(request, gid): ...@@ -237,7 +235,9 @@ def group_ajax_remove_member(request, gid):
@login_required @login_required
def group_ajax_delete(request): 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() group.delete()
return HttpResponse(json.dumps({ return HttpResponse(json.dumps({
'status': 'OK' 'status': 'OK'
...@@ -246,30 +246,27 @@ def group_ajax_delete(request): ...@@ -246,30 +246,27 @@ def group_ajax_delete(request):
@login_required @login_required
def group_ajax_owner_autocomplete(request): def group_ajax_owner_autocomplete(request):
# TODO should be renamed to something like 'user_ajax_autocomplete'
users = ( query = request.POST['q']
User.objects.filter(last_name__istartswith=request.POST['q'])[:5] + users = chain(User.objects.filter(last_name__istartswith=query)[:5],
User.objects.filter(first_name__istartswith=request.POST['q'])[:5] + User.objects.filter(first_name__istartswith=query)[:5],
User.objects.filter(username__istartswith=request.POST['q'])[:5]) User.objects.filter(username__istartswith=query)[:5])
results = map(lambda u: { results = [{'name': user.get_full_name(),
'name': u.get_full_name(), 'neptun': user.username} for user in users]
'neptun': u.username}, users)
return HttpResponse(json.dumps(results, ensure_ascii=False)) return HttpResponse(json.dumps(results, ensure_ascii=False))
@login_required @login_required
def group_ajax_add_new_owner(request, gid): def group_ajax_add_new_owner(request, gid):
if request.user.cloud_details.share_quota == 0: if request.user.cloud_details.share_quota <= 0:
return HttpResponse({'status': 'denied'}) return HttpResponse(json.dumps({'status': 'denied'}))
group = get_object_or_404(Group, id=gid) group = get_object_or_404(Group, id=gid)
member = request.POST['neptun'] 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'}) status = json.dumps({'status': 'Error'})
messages.error(request, _('Invalid NEPTUN code')) messages.error(request, _('Invalid NEPTUN code'))
return HttpResponse(status) return HttpResponse(status)
person, created = Person.objects.get_or_create(code=member) person, created = Person.objects.get_or_create(code=member)
group.owners.add(person) group.owners.add(person)
group.save() group.save()
return HttpResponse(json.dumps({ return HttpResponse(json.dumps({'status': 'OK'}))
'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