Commit acad60f8 authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

User create_groups api call in group creation

parent 1df1b39d
class ItemNotExists(NameError):
pass
class ItemExists(NameError):
pass
import astakos.im.api.backends.errors
(SUCCESS, FAILURE) = range(2)
class MissingIdentifier(IOError):
class BaseBackend(object):
#TODO filled
pass
class SuccessResult():
def __init__(self, data):
self.data = data
@property
def is_success(self):
return True
class BaseBackend(object):
def update_user():
pass
def create_user():
pass
class FailureResult():
def __init__(self, reason):
self.reason = reason
@property
def is_success(self):
return False
# Copyright 2011-2012 GRNET S.A. All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and
# documentation are those of the authors and should not be
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
class ItemNotExists(ValueError):
def __init__(self, type, **kwargs):
fields = " and ".join('%s=%s' % (k,v) for k,v in kwargs.iteritems())
msg = "%(type)s with %(fields)s does not exist."
super(ItemNotExists, self).__init__(msg % locals())
class ItemExists(ValueError):
def __init__(self, type, **kwargs):
fields = " and ".join('%s=%s' % (k,v) for k,v in kwargs.iteritems())
msg = "%(type)s with %(fields)s already exists."
super(ItemExists, self).__init__(msg % locals())
class MultipleItemsExist(ValueError):
def __init__(self, type, **kwargs):
fields = " and ".join('%s=%s' % (k,v) for k,v in kwargs.iteritems())
msg = "There are mulptiple %(type)s with %(fields)s."
super(MultipleItemsExist, self).__init__(msg % locals())
class MissingIdentifier(IOError):
pass
\ No newline at end of file
......@@ -37,9 +37,13 @@ from django.core.exceptions import ObjectDoesNotExist
from functools import wraps
from smtplib import SMTPException
from astakos.im.models import AstakosUser, Resource, Service, RESOURCE_SEPARATOR
from astakos.im.api.backends.base import (BaseBackend, ItemNotExists,
ItemExists, MissingIdentifier)
from astakos.im.models import (
AstakosUser, AstakosGroup, GroupKind, Resource, Service, RESOURCE_SEPARATOR
)
from astakos.im.api.backends.base import BaseBackend, SuccessResult, FailureResult
from astakos.im.api.backends.errors import (
ItemNotExists, ItemExists, MissingIdentifier, MultipleItemsExist
)
from astakos.im.util import reserved_email, model_to_dict
from astakos.im.endpoints.quotaholder import get_quota
......@@ -50,55 +54,53 @@ logger = logging.getLogger(__name__)
DEFAULT_CONTENT_TYPE = None
def safe(propagate_exceptions=False):
def safe(func):
"""Decorator function for views that implement an API method."""
def decorator(func):
@transaction.commit_manually
@wraps(func)
def wrapper(self, *args, **kwargs):
logger.debug('%s %s %s' % (func, args, kwargs))
try:
r = func(self, *args, **kwargs) or ()
except Exception, e:
logger.exception(e)
transaction.rollback()
if propagate_exceptions:
raise e
else:
args = list(args)
args.append(e)
r = args
else:
transaction.commit()
r = filter(bool, r) # filter out None elements
return list(r)
return wrapper
return decorator
@transaction.commit_manually
@wraps(func)
def wrapper(self, *args, **kwargs):
logger.debug('%s %s %s' % (func, args, kwargs))
try:
data = func(self, *args, **kwargs) or ()
except Exception, e:
logger.exception(e)
transaction.rollback()
return FailureResult(getattr(e, 'message', e))
else:
transaction.commit()
return SuccessResult(data)
return wrapper
class DjangoBackend(BaseBackend):
def _lookup_object(self, model, **kwargs):
"""
Returns an object of the specific model having this id.
Returns an object of the specific model matching the given lookup
parameters.
"""
if not kwargs:
raise MissingIdentifier
try:
return model.objects.get(**kwargs)
except model.DoesNotExist:
raise ItemNotExists()
raise ItemNotExists(model._meta.verbose_name, **kwargs)
except model.MultipleObjectsReturned:
raise MultipleItemsExist(model._meta.verbose_name, **kwargs)
def _lookup_user(self, id):
"""
Returns an AstakosUser having this id.
"""
if not isinstance(id, int):
raise TypeError('User id should be of type int')
return self._lookup_object(AstakosUser, id=id)
def _lookup_service(self, id):
"""
Returns an Service having this id.
"""
if not isinstance(id, int):
raise TypeError('Service id should be of type int')
return self._lookup_object(Service, id=id)
def _list(self, model, filter=()):
......@@ -108,7 +110,7 @@ class DjangoBackend(BaseBackend):
return map(lambda o: model_to_dict(o, exclude=[]), q)
def _create_object(self, model, **kwargs):
o = model(**kwargs)
o = model.objects.create(**kwargs)
o.save()
return o
......@@ -120,7 +122,7 @@ class DjangoBackend(BaseBackend):
o.save()
return o
@safe()
@safe
def update_user(self, user_id, renew_token=False, **kwargs):
user = self._update_object(AstakosUser, user_id, save=False, **kwargs)
if renew_token:
......@@ -128,7 +130,7 @@ class DjangoBackend(BaseBackend):
if kwargs or renew_token:
user.save()
@safe()
@safe
def create_user(self, **kwargs):
policies = kwargs.pop('policies', ())
permissions = kwargs.pop('permissions', ())
......@@ -142,8 +144,9 @@ class DjangoBackend(BaseBackend):
u.permissions = permissions
u.policies = policies
u.extended_groups = groups
return self._list(AstakosUser, filter=(u.id,))
@safe()
@safe
def add_policies(self, user_id, update=False, policies=()):
user = self._lookup_user(user_id)
rejected = []
......@@ -156,10 +159,9 @@ class DjangoBackend(BaseBackend):
user.add_policy(service, resource, uplimit, update)
except (ObjectDoesNotExist, IntegrityError), e:
append((service, resource, e))
if rejected:
raise Exception(rejected)
@safe()
return rejected
@safe
def remove_policies(self, user_id, policies=()):
user = self._lookup_user(user_id)
if not user:
......@@ -173,10 +175,8 @@ class DjangoBackend(BaseBackend):
user.delete_policy(service, resource)
except ObjectDoesNotExist, e:
append((service, resource, e))
if rejected:
raise Exception(rejected)
@safe()
return rejected
@safe
def add_permissions(self, user_id, permissions=()):
user = self._lookup_user(user_id)
rejected = []
......@@ -186,10 +186,9 @@ class DjangoBackend(BaseBackend):
user.add_permission(p)
except IntegrityError, e:
append((p, e))
if rejected:
raise Exception(rejected)
@safe()
return rejected
@safe
def remove_permissions(self, user_id, permissions=()):
user = self._lookup_user(user_id)
rejected = []
......@@ -199,10 +198,9 @@ class DjangoBackend(BaseBackend):
user.remove_permission(p)
except (ObjectDoesNotExist, IntegrityError), e:
append((p, e))
if rejected:
raise Exception(rejected)
@safe()
return rejected
@safe
def invite_users(self, senderid, recipients=()):
user = self._lookup_user(senderid)
rejected = []
......@@ -212,14 +210,13 @@ class DjangoBackend(BaseBackend):
user.invite(r.get('email'), r.get('realname'))
except (IntegrityError, SMTPException), e:
append((email, e))
if rejected:
raise Exception(rejected)
@safe(propagate_exceptions=True)
return rejected
@safe
def list_users(self, filter=()):
return self._list(AstakosUser, filter=filter)
@safe(propagate_exceptions=True)
@safe
def get_resource_usage(self, user_id):
user = self._lookup_user(user_id)
c, data = get_quota((user,))
......@@ -240,23 +237,24 @@ class DjangoBackend(BaseBackend):
append(d)
return resources
@safe(propagate_exceptions=True)
@safe
def list_resources(self, filter=()):
return self._list(Resource, filter=filter)
@safe()
@safe
def create_service(self, **kwargs):
resources = kwargs.pop('resources', ())
s = self._create_object(Service, **kwargs)
s.resources = resources
return self._list(Service, filter=(s.id,))
@safe()
@safe
def remove_services(self, ids=()):
# TODO return information for unknown ids
q = Service.objects.filter(id__in=ids)
q.delete()
@safe()
@safe
def update_service(self, service_id, renew_token=False, **kwargs):
s = self._update_object(Service, service_id, save=False, **kwargs)
if renew_token:
......@@ -265,7 +263,7 @@ class DjangoBackend(BaseBackend):
if kwargs or renew_token:
s.save()
@safe()
@safe
def add_resources(self, service_id, update=False, resources=()):
s = self._lookup_service(service_id)
rejected = []
......@@ -282,26 +280,29 @@ class DjangoBackend(BaseBackend):
resource = self._create_object(Resource, service=s, **rr)
except Exception, e:
append((r, e))
if rejected:
raise Exception(rejected)
return rejected
@safe()
@safe
def remove_resources(self, service_id, ids=()):
# TODO return information for unknown ids
q = Resource.objects.filter(service__id=service_id,
id__in=ids)
q.delete()
@safe()
@safe
def create_group(self, **kwargs):
policies = kwargs.pop('policies', ())
permissions = kwargs.pop('permissions', ())
members = kwargs.pop('members', ())
owners = kwargs.pop('owners', ())
kwargs['kind'] = self._lookup_object(
GroupKind, name=kwargs.get('kind', 'course')
)
g = self._create_object(AstakosGroup, **kwargs)
g.permissions = permissions
g.policies = policies
g.members = members
g.owners = owners
\ No newline at end of file
# g.members = members
g.owners = owners
return self._list(AstakosGroup, filter=(g.id,))
\ No newline at end of file
......@@ -32,7 +32,7 @@
# or implied, of GRNET S.A.
from astakos.im.api.spec import AstakosAPI
from backends import get_backend
from astakos.im.api.backends import get_backend
from commissioning import (Callpoint,
# CommissionException,
......@@ -49,7 +49,7 @@ from commissioning import (Callpoint,
# from .models import (Holder, Entity, Policy, Holding,
# Commission, Provision, ProvisionLog, now)
class AstakosDjangoDBCallpoint():
class AstakosCallpoint():
api_spec = AstakosAPI()
......@@ -159,4 +159,4 @@ class AstakosDjangoDBCallpoint():
rejected = (b.create_group(**g) for g in groups)
return rejected
API_Callpoint = AstakosDjangoDBCallpoint
API_Callpoint = AstakosCallpoint
......@@ -42,10 +42,10 @@ class Boolean(Integer):
Boolean = Boolean()
class GroupKind(Integer):
def init(self):
self.opts.update({'minimum': 1, 'maximum': 5})
GroupKind = GroupKind()
# class GroupKind(Integer):
# def init(self):
# self.opts.update({'minimum': 1, 'maximum': 5})
# GroupKind = GroupKind()
Timepoint = Text(classname='Timepoint', maxlen=24)
......@@ -280,7 +280,7 @@ class AstakosAPI(Specificator):
self,
groups=ListOf(
name=Name,
kind=GroupKind,
kind=Name,
homepage=Url,
desc=Text(),
policies=ListOf(resource=Name, upimit=Nonnegative),
......@@ -303,7 +303,7 @@ class AstakosAPI(Specificator):
def search_groups(self, key=Name):
return ListOf(
group=Name,
kind=GroupKind,
kind=Nonnegative,
homepage=Url,
desc=Text(),
creation_date=Timepoint,
......@@ -319,7 +319,7 @@ class AstakosAPI(Specificator):
def list_groups(self):
return ListOf(
group=Name,
kind=GroupKind,
kind=Nonnegative,
homepage=Url,
desc=Text(),
creation_date=Timepoint,
......
......@@ -572,18 +572,22 @@ class AstakosGroupCreationForm(forms.ModelForm):
map(add_field,
((k, v) for k,v in qd.iteritems() if k.endswith('_uplimit'))
)
def policies(self):
def clean(self):
self.cleaned_data['policies'] = []
append = self.cleaned_data['policies'].append
tbd = []
for name, uplimit in self.cleaned_data.iteritems():
subs = name.split('_uplimit')
if len(subs) == 2:
tbd.append(name)
prefix, suffix = subs
# # yield only those having a value
# if not value:
# continue
s, r = prefix.split(RESOURCE_SEPARATOR)
yield dict(service=s, resource=r, uplimit=uplimit)
append(dict(service=s, resource=r, uplimit=uplimit))
for name in tbd:
del self.cleaned_data[name]
return self.cleaned_data
class AstakosGroupUpdateForm(forms.ModelForm):
class Meta:
......
......@@ -47,7 +47,7 @@ class Command(BaseCommand):
def handle(self, *args, **options):
try:
register_resources(Resource.objects.all())
register_users(AstakosUser.objects.filter(disturbed_quota=True))
register_users(AstakosUser.objects.all())
except BaseException, e:
logger.exception(e)
raise CommandError("Syncing failed.")
......@@ -22,12 +22,11 @@ class Migration(DataMigration):
sn, dict = args
url = dict.get('url')
resources = dict.get('resources') or ()
s, created = orm.Service.objects.get_or_create(name=sn,
defaults={'url': url})
if not created and not s.url:
s.url = url
s.save()
s, created = orm.Service.objects.get_or_create(
name=sn,
defaults={'url': url}
)
for r in resources:
try:
rn = r.pop('name', '')
......@@ -43,8 +42,10 @@ class Migration(DataMigration):
q, created = orm.AstakosGroupQuota.objects.get_or_create(
group=default,
resource=r,
uplimit=uplimit,
limit=0)
defaults={
'uplimit':uplimit,
}
)
map(create_policies, SERVICES.iteritems())
def backwards(self, orm):
......
......@@ -85,8 +85,6 @@ urlpatterns = patterns('astakos.im.views',
'disapprove_member', {}, name='disapprove_member'),
url(r'^group/create/?$', 'group_create_list', {},
name='group_create_list'),
url(r'^group/create_demo/?$', 'group_create_demo', {},
name='group_create_demo'),
)
if EMAILCHANGE_ENABLED:
......
......@@ -87,7 +87,7 @@ from astakos.im.endpoints.quotaholder import timeline_charge
from astakos.im.settings import (COOKIE_NAME, COOKIE_DOMAIN, LOGOUT_NEXT,
LOGGING_LEVEL, PAGINATE_BY)
from astakos.im.tasks import request_billing
from astakos.im.api.callpoint import AstakosDjangoDBCallpoint
from astakos.im.api.callpoint import AstakosCallpoint
logger = logging.getLogger(__name__)
......@@ -95,7 +95,7 @@ logger = logging.getLogger(__name__)
DB_REPLACE_GROUP_SCHEME = """REPLACE(REPLACE("auth_group".name, 'http://', ''),
'https://', '')"""
callpoint = AstakosDjangoDBCallpoint()
callpoint = AstakosCallpoint()
def render_response(template, tab=None, status=200, reset_cookie=False,
context_instance=None, **kwargs):
......@@ -667,10 +667,53 @@ def change_email(request, activation_key=None,
@signed_terms_required
@login_required
def group_add(request, kind_name='default'):
result = callpoint.list_resources()
resource_catalog = {'resources':defaultdict(defaultdict),
'groups':defaultdict(list)}
if result.is_success:
for r in result.data:
service = r.get('service', '')
name = r.get('name', '')
group = r.get('group', '')
unit = r.get('unit', '')
fullname = '%s%s%s' % (service, RESOURCE_SEPARATOR, name)
resource_catalog['resources'][fullname] = dict(unit=unit)
resource_catalog['groups'][group].append(fullname)
resource_catalog = dict(resource_catalog)
for k, v in resource_catalog.iteritems():
resource_catalog[k] = dict(v)
else:
messages.error(
request,
'Unable to retrieve system resources: %s' % result.reason
)
try:
kind = GroupKind.objects.get(name=kind_name)
except:
return HttpResponseBadRequest(_('No such group kind'))
resource_presentation = {
'compute': {
'help_text':'group compute help text',
},
'storage': {
'help_text':'group storage help text',
},
'pithos+.diskspace': {
'help_text':'resource pithos+.diskspace help text',
},
'cyclades.vm': {
'help_text':'resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text resource cyclades.vm help text',
},
'cyclades.disksize': {
'help_text':'resource cyclades.disksize help text',
},
'cyclades.ram': {
'help_text':'resource cyclades.ram help text',
}
}
post_save_redirect = '/im/group/%(id)s/'
context_processors = None
......@@ -678,57 +721,46 @@ def group_add(request, kind_name='default'):
model=None,
form_class=AstakosGroupCreationForm
)
resources = dict(
(str(r.id), r) for r in Resource.objects.select_related().all())
policies = []
if request.method == 'POST':
form = form_class(request.POST, request.FILES, resources=resources)
form = form_class(request.POST, request.FILES)
if form.is_valid():
new_object = form.save()