-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
--- /dev/null
+# 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
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
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=()):
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
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:
if kwargs or renew_token:
user.save()
- @safe()
+ @safe
def create_user(self, **kwargs):
policies = kwargs.pop('policies', ())
permissions = kwargs.pop('permissions', ())
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 = []
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:
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 = []
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 = []
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 = []
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,))
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:
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 = []
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
# 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,
# from .models import (Holder, Entity, Policy, Holding,
# Commission, Provision, ProvisionLog, now)
-class AstakosDjangoDBCallpoint():
+class AstakosCallpoint():
api_spec = AstakosAPI()
rejected = (b.create_group(**g) for g in groups)
return rejected
-API_Callpoint = AstakosDjangoDBCallpoint
+API_Callpoint = AstakosCallpoint
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)
self,
groups=ListOf(
name=Name,
- kind=GroupKind,
+ kind=Name,
homepage=Url,
desc=Text(),
policies=ListOf(resource=Name, upimit=Nonnegative),
def search_groups(self, key=Name):
return ListOf(
group=Name,
- kind=GroupKind,
+ kind=Nonnegative,
homepage=Url,
desc=Text(),
creation_date=Timepoint,
def list_groups(self):
return ListOf(
group=Name,
- kind=GroupKind,
+ kind=Nonnegative,
homepage=Url,
desc=Text(),
creation_date=Timepoint,
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:
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.")
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', '')
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):
'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:
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__)
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):
@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
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()
-
- # save owner
- new_object.owners = [request.user]
-
- # save quota policies
- for (rid, uplimit) in form.resources():
- try:
- r = resources[rid]
- except KeyError, e:
- logger.exception(e)
- # TODO Should I stay or should I go???
- continue
- else:
- new_object.astakosgroupquota_set.create(
- resource=r,
- uplimit=uplimit
- )
- policies.append('%s %d' % (r, uplimit))
- msg = _("The %(verbose_name)s was created successfully.") %\
- {"verbose_name": model._meta.verbose_name}
- messages.success(request, msg, fail_silently=True)
-
- # send notification
- try:
- send_group_creation_notification(
- template_name='im/group_creation_notification.txt',
- dictionary={
- 'group': new_object,
- 'owner': request.user,
- 'policies': policies,
- }
- )
- except SendNotificationError, e:
- messages.error(request, e, fail_silently=True)
- return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
+ d = form.cleaned_data.copy()
+ d['owners'] = [request.user]
+ result = callpoint.create_groups((d,)).next()
+ if result.is_success:
+ new_object = result.data[0]
+ msg = _("The %(verbose_name)s was created successfully.") %\
+ {"verbose_name": model._meta.verbose_name}
+ messages.success(request, msg, fail_silently=True)
+
+# # send notification
+# try:
+# send_group_creation_notification(
+# template_name='im/group_creation_notification.txt',
+# dictionary={
+# 'group': new_object,
+# 'owner': request.user,
+# 'policies': list(form.cleaned_data['policies']),
+# }
+# )
+# except SendNotificationError, e:
+# messages.error(request, e, fail_silently=True)
+ return HttpResponseRedirect(post_save_redirect % new_object)
+ else:
+ msg = _("The %(verbose_name)s creation failed: %(reason)s.") %\
+ {"verbose_name": model._meta.verbose_name,
+ "reason":result.reason}
+ messages.error(request, msg, fail_silently=True)
else:
now = datetime.now()
data = {
'kind': kind
}
- form = form_class(data, resources=resources)
+ form = form_class(data)
# Create the template, context, response
- template_name = "%s/%s_form.html" % (
+ template_name = "%s/%s_form_demo.html" % (
model._meta.app_label,
model._meta.object_name.lower()
)
c = RequestContext(request, {
'form': form,
'kind': kind,
+ 'resource_catalog':resource_catalog,
+ 'resource_presentation':resource_presentation,
}, context_processors)
return HttpResponse(t.render(c))
-
@signed_terms_required
@login_required
def group_list(request):
entry['plural'] = engine.plural(entry.get('name'))
return entry
- try:
- data = callpoint.get_user_status(request.user.id)
- except Exception, e:
- data = None
- messages.error(request, e)
+ result = callpoint.get_user_status(request.user.id)
+ if result.is_success:
+ backenddata = map(with_class, result.data)
+ data = map(pluralize, result.data)
else:
- backenddata = map(with_class, data)
- data = map(pluralize, data)
+ data = None
+ messages.error(request, result.reason)
return render_response('im/resource_list.html',
data=data,
context_instance=get_context(request))
-@signed_terms_required
-@login_required
-def group_create_demo(request, kind_name='default'):
- resources = callpoint.list_resources()
- resource_catalog = {'resources':defaultdict(defaultdict),
- 'groups':defaultdict(list)}
- for r in resources:
- 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)
- try:
- kind = GroupKind.objects.get(name=kind_name)
- except:
- return HttpResponseBadRequest(_('No such group kind'))
-
- post_save_redirect = '/im/group/%(id)s/'
- context_processors = None
- model, form_class = get_model_and_form_class(
- model=None,
- form_class=AstakosGroupCreationForm
- )
-
- if request.method == 'POST':
- form = form_class(request.POST, request.FILES)
- if form.is_valid():
- new_object = form.save()
- new_object.policies = form.policies()
-
- # save owner
- new_object.owners = [request.user]
-
- msg = _("The %(verbose_name)s was created successfully.") %\
- {"verbose_name": model._meta.verbose_name}
- messages.success(request, msg, fail_silently=True)
-
- # send notification
- try:
- send_group_creation_notification(
- template_name='im/group_creation_notification.txt',
- dictionary={
- 'group': new_object,
- 'owner': request.user,
- 'policies': list(form.policies()),
- }
- )
- except SendNotificationError, e:
- messages.error(request, e, fail_silently=True)
- return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
- else:
- now = datetime.now()
- data = {
- 'kind': kind
- }
- form = form_class(data)
-
- 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',
-
- }
-
- }
-
- # Create the template, context, response
- template_name = "%s/%s_form_demo.html" % (
- model._meta.app_label,
- model._meta.object_name.lower()
- )
- t = template_loader.get_template(template_name)
- c = RequestContext(request, {
- 'form': form,
- 'kind': kind,
- 'resource_catalog':resource_catalog,
- 'resource_presentation':resource_presentation
- }, context_processors)
- return HttpResponse(t.render(c))
-
def group_create_list(request):
form = PickResourceForm()