Commit 2fad1177 authored by Giorgos Korfiatis's avatar Giorgos Korfiatis Committed by Christos Stavrakakis
Browse files

astakos: Preserve existing quota on default change

View the resource defaults as a skeleton to be consulted when accepting
a new user. All users keep their quota in AstakosUserQuota.

Operate resource-modify in bulk, in order to avoiding updating the
quotaholder separately for each resource.
parent 473f2d23
...@@ -300,11 +300,9 @@ Setting quota limits ...@@ -300,11 +300,9 @@ Setting quota limits
Set default quota Set default quota
````````````````` `````````````````
To inspect current default base quota limits, run::
In 20-snf-astakos-app-settings.conf, # snf-manage resource-list
uncomment the default setting ``ASTAKOS_SERVICES``
and customize the ``'uplimit'`` values.
These are the default base quota for all users.
You can modify the default base quota limit for all future users with:: You can modify the default base quota limit for all future users with::
......
...@@ -40,7 +40,7 @@ from astakos.im import functions ...@@ -40,7 +40,7 @@ from astakos.im import functions
from astakos.im import settings from astakos.im import settings
from astakos.im import forms from astakos.im import forms
from astakos.im.quotas import qh_sync_user from astakos.im.quotas import qh_sync_new_user
import astakos.im.messages as astakos_messages import astakos.im.messages as astakos_messages
...@@ -257,7 +257,7 @@ class ActivationBackend(object): ...@@ -257,7 +257,7 @@ class ActivationBackend(object):
default=lambda obj: default=lambda obj:
str(obj)) str(obj))
user.save() user.save()
qh_sync_user(user) qh_sync_new_user(user)
if user.is_rejected: if user.is_rejected:
logger.warning("User has previously been " logger.warning("User has previously been "
......
...@@ -37,7 +37,7 @@ from django.utils import simplejson as json ...@@ -37,7 +37,7 @@ from django.utils import simplejson as json
from snf_django.management import utils from snf_django.management import utils
from astakos.im.models import Resource from astakos.im.models import Resource
from astakos.im.register import update_resource from astakos.im.register import update_resources
from ._common import show_resource_value, style_options, check_style, units from ._common import show_resource_value, style_options, check_style, units
...@@ -139,10 +139,14 @@ class Command(BaseCommand): ...@@ -139,10 +139,14 @@ class Command(BaseCommand):
else: else:
resources = [self.get_resource(resource_name)] resources = [self.get_resource(resource_name)]
updates = []
for resource in resources: for resource in resources:
limit = config.get(resource.name) limit = config.get(resource.name)
if limit is not None: if limit is not None:
self.change_resource_limit(resource, limit) limit = self.parse_limit(limit)
updates.append((resource, limit))
if updates:
update_resources(updates)
def change_interactive(self, resource_name, _placeholder): def change_interactive(self, resource_name, _placeholder):
if resource_name is None: if resource_name is None:
...@@ -150,6 +154,7 @@ class Command(BaseCommand): ...@@ -150,6 +154,7 @@ class Command(BaseCommand):
else: else:
resources = [self.get_resource(resource_name)] resources = [self.get_resource(resource_name)]
updates = []
for resource in resources: for resource in resources:
self.stdout.write("Resource '%s' (%s)\n" % self.stdout.write("Resource '%s' (%s)\n" %
(resource.name, resource.desc)) (resource.name, resource.desc))
...@@ -166,15 +171,24 @@ class Command(BaseCommand): ...@@ -166,15 +171,24 @@ class Command(BaseCommand):
value = units.parse(response) value = units.parse(response)
except units.ParseError: except units.ParseError:
continue continue
update_resource(resource, value) updates.append((resource, value))
break break
if updates:
self.stdout.write("Updating...\n")
update_resources(updates)
def parse_limit(self, limit):
try:
if isinstance(limit, (int, long)):
return limit
if isinstance(limit, basestring):
return units.parse(limit)
raise units.ParseError()
except units.ParseError:
m = ("Limit should be an integer, optionally followed by a unit,"
" or 'inf'.")
raise CommandError(m)
def change_resource_limit(self, resource, limit): def change_resource_limit(self, resource, limit):
if not isinstance(limit, (int, long)): limit = self.parse_limit(limit)
try: update_resources([(resource, limit)])
limit = units.parse(limit)
except units.ParseError:
m = ("Limit should be an integer, optionally followed "
"by a unit.")
raise CommandError(m)
update_resource(resource, limit)
...@@ -43,7 +43,7 @@ from django.core.exceptions import ValidationError ...@@ -43,7 +43,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import validate_email from django.core.validators import validate_email
from synnefo.util import units from synnefo.util import units
from astakos.im.models import AstakosUser, Resource from astakos.im.models import AstakosUser, AstakosUserQuota
from astakos.im import quotas from astakos.im import quotas
from astakos.im import activation_backends from astakos.im import activation_backends
from ._common import (remove_user_permission, add_user_permission, is_uuid, from ._common import (remove_user_permission, add_user_permission, is_uuid,
...@@ -345,14 +345,14 @@ class Command(BaseCommand): ...@@ -345,14 +345,14 @@ class Command(BaseCommand):
raise CommandError(m) raise CommandError(m)
try: try:
quota, default_capacity = user.get_resource_policy(resource) quota = user.get_resource_policy(resource)
except Resource.DoesNotExist: except AstakosUserQuota.DoesNotExist:
raise CommandError("No such resource: %s" % resource) raise CommandError("No such resource: %s" % resource)
default_capacity = quota.resource.uplimit
if not force: if not force:
s_default = show_resource_value(default_capacity, resource, style) s_default = show_resource_value(default_capacity, resource, style)
s_current = (show_resource_value(quota.capacity, resource, style) s_current = show_resource_value(quota.capacity, resource, style)
if quota is not None else 'default')
s_capacity = (show_resource_value(capacity, resource, style) s_capacity = (show_resource_value(capacity, resource, style)
if capacity != 'default' else capacity) if capacity != 'default' else capacity)
self.stdout.write("user: %s (%s)\n" % (user.uuid, user.username)) self.stdout.write("user: %s (%s)\n" % (user.uuid, user.username))
...@@ -366,14 +366,5 @@ class Command(BaseCommand): ...@@ -366,14 +366,5 @@ class Command(BaseCommand):
return return
if capacity == 'default': if capacity == 'default':
try: capacity = default_capacity
quotas.remove_base_quota(user, resource) quotas.update_base_quota(quota, capacity)
except Exception as e:
import traceback
traceback.print_exc()
raise CommandError("Failed to remove policy: %s" % e)
else:
try:
quotas.add_base_quota(user, resource, capacity)
except Exception as e:
raise CommandError("Failed to add policy: %s" % e)
This diff is collapsed.
...@@ -519,13 +519,8 @@ class AstakosUser(User): ...@@ -519,13 +519,8 @@ class AstakosUser(User):
return self.astakosuserquota_set.select_related().all() return self.astakosuserquota_set.select_related().all()
def get_resource_policy(self, resource): def get_resource_policy(self, resource):
resource = Resource.objects.get(name=resource) return AstakosUserQuota.objects.select_related("resource").\
default_capacity = resource.uplimit get(user=self, resource__name=resource)
try:
policy = AstakosUserQuota.objects.get(user=self, resource=resource)
return policy, default_capacity
except AstakosUserQuota.DoesNotExist:
return None, default_capacity
def update_uuid(self): def update_uuid(self):
while not self.uuid: while not self.uuid:
......
...@@ -121,16 +121,6 @@ def _set_user_quota(quotas): ...@@ -121,16 +121,6 @@ def _set_user_quota(quotas):
qh.set_quota(q) qh.set_quota(q)
def get_default_quota():
_DEFAULT_QUOTA = {}
resources = Resource.objects.select_related('service').all()
for resource in resources:
capacity = resource.uplimit
_DEFAULT_QUOTA[resource.full_name()] = capacity
return _DEFAULT_QUOTA
SYSTEM = 'system' SYSTEM = 'system'
PENDING_APP_RESOURCE = 'astakos.pending_app' PENDING_APP_RESOURCE = 'astakos.pending_app'
...@@ -153,40 +143,17 @@ def get_pending_app_quota(user): ...@@ -153,40 +143,17 @@ def get_pending_app_quota(user):
return quota[SYSTEM][PENDING_APP_RESOURCE] return quota[SYSTEM][PENDING_APP_RESOURCE]
def add_base_quota(user, resource, capacity): def update_base_quota(quota, capacity):
resource = Resource.objects.get(name=resource) quota.capacity = capacity
user = get_user_for_update(user.id) quota.save()
obj, created = AstakosUserQuota.objects.get_or_create( qh_sync_locked_user(quota.user)
user=user, resource=resource, defaults={
'capacity': capacity,
})
if not created:
obj.capacity = capacity
obj.save()
qh_sync_locked_user(user)
def remove_base_quota(user, resource):
user = get_user_for_update(user.id)
AstakosUserQuota.objects.filter(
user=user, resource__name=resource).delete()
qh_sync_locked_user(user)
def initial_quotas(users): def initial_quotas(users):
users = list(users)
initial = {}
default_quotas = get_default_quota()
for user in users:
uuid = user.uuid
source_quota = {SYSTEM: dict(default_quotas)}
initial[uuid] = source_quota
userids = [user.pk for user in users] userids = [user.pk for user in users]
objs = AstakosUserQuota.objects.select_related() objs = AstakosUserQuota.objects.select_related()
orig_quotas = objs.filter(user__pk__in=userids) orig_quotas = objs.filter(user__pk__in=userids)
initial = {}
for user_quota in orig_quotas: for user_quota in orig_quotas:
uuid = user_quota.user.uuid uuid = user_quota.user.uuid
user_init = initial.get(uuid, {}) user_init = initial.get(uuid, {})
...@@ -304,6 +271,21 @@ def qh_sync_user(user): ...@@ -304,6 +271,21 @@ def qh_sync_user(user):
qh_sync_users([user]) qh_sync_users([user])
def qh_sync_new_users(users):
entries = []
for resource in Resource.objects.all():
for user in users:
entries.append(
AstakosUserQuota(user=user, resource=resource,
capacity=resource.uplimit))
AstakosUserQuota.objects.bulk_create(entries)
qh_sync_users(users)
def qh_sync_new_user(user):
qh_sync_new_users([user])
def members_to_sync(project): def members_to_sync(project):
objs = ProjectMembership.objects.select_related('person') objs = ProjectMembership.objects.select_related('person')
memberships = objs.filter(project=project, memberships = objs.filter(project=project,
...@@ -316,24 +298,14 @@ def qh_sync_project(project): ...@@ -316,24 +298,14 @@ def qh_sync_project(project):
qh_sync_users(users) qh_sync_users(users)
def qh_change_resource_limit(resource):
objs = AstakosUser.objects.filter(
Q(moderated=True, is_rejected=False) & ~Q(policy=resource))
users = objs.order_by('id').select_for_update()
quota = astakos_users_quotas(users)
_set_user_quota(quota)
def qh_sync_new_resource(resource): def qh_sync_new_resource(resource):
users = AstakosUser.objects.filter( users = AstakosUser.objects.filter(
moderated=True, is_rejected=False).order_by('id').select_for_update() moderated=True, is_rejected=False).order_by('id').select_for_update()
resource_name = resource.name entries = []
limit = resource.uplimit
data = []
for user in users: for user in users:
uuid = user.uuid entries.append(
key = uuid, SYSTEM, resource_name AstakosUserQuota(user=user, resource=resource,
data.append((key, limit)) capacity=resource.uplimit))
AstakosUserQuota.objects.bulk_create(entries)
qh.set_quota(data) qh_sync_users(users)
...@@ -104,19 +104,19 @@ def add_resource(resource_dict): ...@@ -104,19 +104,19 @@ def add_resource(resource_dict):
return r, exists return r, exists
def update_resource(resource, uplimit): def update_resources(updates):
old_uplimit = resource.uplimit resources = []
if uplimit == old_uplimit: for resource, uplimit in updates:
logger.info("Resource %s has limit %s; no need to update." resources.append(resource)
% (resource.name, uplimit)) old_uplimit = resource.uplimit
return [] if uplimit == old_uplimit:
else: logger.info("Resource %s has limit %s; no need to update."
resource.uplimit = uplimit % (resource.name, uplimit))
resource.save() else:
logger.info("Updated resource %s with limit %s." resource.uplimit = uplimit
% (resource.name, uplimit)) resource.save()
affected = quotas.qh_change_resource_limit(resource) logger.info("Updated resource %s with limit %s."
return affected % (resource.name, uplimit))
def get_resources(resources=None, services=None): def get_resources(resources=None, services=None):
......
...@@ -64,14 +64,14 @@ class QuotaAPITest(TestCase): ...@@ -64,14 +64,14 @@ class QuotaAPITest(TestCase):
"service_origin": "service1", "service_origin": "service1",
"allow_in_projects": True} "allow_in_projects": True}
r, _ = register.add_resource(resource11) r, _ = register.add_resource(resource11)
register.update_resource(r, 100) register.update_resources([(r, 100)])
resource12 = {"name": "service1.resource12", resource12 = {"name": "service1.resource12",
"desc": "resource11 desc", "desc": "resource11 desc",
"service_type": "type1", "service_type": "type1",
"service_origin": "service1", "service_origin": "service1",
"unit": "bytes"} "unit": "bytes"}
r, _ = register.add_resource(resource12) r, _ = register.add_resource(resource12)
register.update_resource(r, 1024) register.update_resources([(r, 1024)])
# create user # create user
user = get_local_user('test@grnet.gr') user = get_local_user('test@grnet.gr')
...@@ -91,7 +91,7 @@ class QuotaAPITest(TestCase): ...@@ -91,7 +91,7 @@ class QuotaAPITest(TestCase):
"service_origin": "service2", "service_origin": "service2",
"allow_in_projects": False} "allow_in_projects": False}
r, _ = register.add_resource(resource21) r, _ = register.add_resource(resource21)
register.update_resource(r, 3) register.update_resources([(r, 3)])
resource_names = [r['name'] for r in resource_names = [r['name'] for r in
[resource11, resource12, resource21]] [resource11, resource12, resource21]]
......
...@@ -48,14 +48,14 @@ class ProjectAPITest(TestCase): ...@@ -48,14 +48,14 @@ class ProjectAPITest(TestCase):
"service_origin": "service1", "service_origin": "service1",
"allow_in_projects": True} "allow_in_projects": True}
r, _ = register.add_resource(resource11) r, _ = register.add_resource(resource11)
register.update_resource(r, 100) register.update_resources([(r, 100)])
resource12 = {"name": "service1.resource12", resource12 = {"name": "service1.resource12",
"desc": "resource11 desc", "desc": "resource11 desc",
"service_type": "type1", "service_type": "type1",
"service_origin": "service1", "service_origin": "service1",
"unit": "bytes"} "unit": "bytes"}
r, _ = register.add_resource(resource12) r, _ = register.add_resource(resource12)
register.update_resource(r, 1024) register.update_resources([(r, 1024)])
# create user # create user
self.user1 = get_local_user("test@grnet.gr", moderated=True) self.user1 = get_local_user("test@grnet.gr", moderated=True)
...@@ -76,7 +76,7 @@ class ProjectAPITest(TestCase): ...@@ -76,7 +76,7 @@ class ProjectAPITest(TestCase):
"service_origin": "astakos_account", "service_origin": "astakos_account",
"allow_in_projects": False} "allow_in_projects": False}
r, _ = register.add_resource(pending_app) r, _ = register.add_resource(pending_app)
register.update_resource(r, 3) register.update_resources([(r, 3)])
def create(self, app, headers): def create(self, app, headers):
dump = json.dumps(app) dump = json.dumps(app)
...@@ -618,7 +618,7 @@ class TestProjects(TestCase): ...@@ -618,7 +618,7 @@ class TestProjects(TestCase):
self.member_client = get_user_client("member@synnefo.org") self.member_client = get_user_client("member@synnefo.org")
self.member2_client = get_user_client("member2@synnefo.org") self.member2_client = get_user_client("member2@synnefo.org")
quotas.qh_sync_users(AstakosUser.objects.all()) quotas.qh_sync_new_users(AstakosUser.objects.all())
def tearDown(self): def tearDown(self):
Service.objects.all().delete() Service.objects.all().delete()
...@@ -671,7 +671,8 @@ class TestProjects(TestCase): ...@@ -671,7 +671,8 @@ class TestProjects(TestCase):
@im_settings(PROJECT_ADMINS=['uuid1']) @im_settings(PROJECT_ADMINS=['uuid1'])
def test_applications(self): def test_applications(self):
# let user have 2 pending applications # let user have 2 pending applications
quotas.add_base_quota(self.user, 'astakos.pending_app', 2) q = self.user.get_resource_policy('astakos.pending_app')
quotas.update_base_quota(q, 2)
r = self.user_client.get(reverse('project_add'), follow=True) r = self.user_client.get(reverse('project_add'), follow=True)
self.assertRedirects(r, reverse('project_add')) self.assertRedirects(r, reverse('project_add'))
......
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 to comment