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
Set default quota
`````````````````
To inspect current default base quota limits, run::
In 20-snf-astakos-app-settings.conf,
uncomment the default setting ``ASTAKOS_SERVICES``
and customize the ``'uplimit'`` values.
These are the default base quota for all users.
# snf-manage resource-list
You can modify the default base quota limit for all future users with::
......
......@@ -40,7 +40,7 @@ from astakos.im import functions
from astakos.im import settings
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
......@@ -257,7 +257,7 @@ class ActivationBackend(object):
default=lambda obj:
str(obj))
user.save()
qh_sync_user(user)
qh_sync_new_user(user)
if user.is_rejected:
logger.warning("User has previously been "
......
......@@ -37,7 +37,7 @@ from django.utils import simplejson as json
from snf_django.management import utils
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
......@@ -139,10 +139,14 @@ class Command(BaseCommand):
else:
resources = [self.get_resource(resource_name)]
updates = []
for resource in resources:
limit = config.get(resource.name)
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):
if resource_name is None:
......@@ -150,6 +154,7 @@ class Command(BaseCommand):
else:
resources = [self.get_resource(resource_name)]
updates = []
for resource in resources:
self.stdout.write("Resource '%s' (%s)\n" %
(resource.name, resource.desc))
......@@ -166,15 +171,24 @@ class Command(BaseCommand):
value = units.parse(response)
except units.ParseError:
continue
update_resource(resource, value)
updates.append((resource, value))
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):
if not isinstance(limit, (int, long)):
try:
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)
limit = self.parse_limit(limit)
update_resources([(resource, limit)])
......@@ -43,7 +43,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import validate_email
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 activation_backends
from ._common import (remove_user_permission, add_user_permission, is_uuid,
......@@ -345,14 +345,14 @@ class Command(BaseCommand):
raise CommandError(m)
try:
quota, default_capacity = user.get_resource_policy(resource)
except Resource.DoesNotExist:
quota = user.get_resource_policy(resource)
except AstakosUserQuota.DoesNotExist:
raise CommandError("No such resource: %s" % resource)
default_capacity = quota.resource.uplimit
if not force:
s_default = show_resource_value(default_capacity, resource, style)
s_current = (show_resource_value(quota.capacity, resource, style)
if quota is not None else 'default')
s_current = show_resource_value(quota.capacity, resource, style)
s_capacity = (show_resource_value(capacity, resource, style)
if capacity != 'default' else capacity)
self.stdout.write("user: %s (%s)\n" % (user.uuid, user.username))
......@@ -366,14 +366,5 @@ class Command(BaseCommand):
return
if capacity == 'default':
try:
quotas.remove_base_quota(user, resource)
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)
capacity = default_capacity
quotas.update_base_quota(quota, capacity)
This diff is collapsed.
......@@ -519,13 +519,8 @@ class AstakosUser(User):
return self.astakosuserquota_set.select_related().all()
def get_resource_policy(self, resource):
resource = Resource.objects.get(name=resource)
default_capacity = resource.uplimit
try:
policy = AstakosUserQuota.objects.get(user=self, resource=resource)
return policy, default_capacity
except AstakosUserQuota.DoesNotExist:
return None, default_capacity
return AstakosUserQuota.objects.select_related("resource").\
get(user=self, resource__name=resource)
def update_uuid(self):
while not self.uuid:
......
......@@ -121,16 +121,6 @@ def _set_user_quota(quotas):
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'
PENDING_APP_RESOURCE = 'astakos.pending_app'
......@@ -153,40 +143,17 @@ def get_pending_app_quota(user):
return quota[SYSTEM][PENDING_APP_RESOURCE]
def add_base_quota(user, resource, capacity):
resource = Resource.objects.get(name=resource)
user = get_user_for_update(user.id)
obj, created = AstakosUserQuota.objects.get_or_create(
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 update_base_quota(quota, capacity):
quota.capacity = capacity
quota.save()
qh_sync_locked_user(quota.user)
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]
objs = AstakosUserQuota.objects.select_related()
orig_quotas = objs.filter(user__pk__in=userids)
initial = {}
for user_quota in orig_quotas:
uuid = user_quota.user.uuid
user_init = initial.get(uuid, {})
......@@ -304,6 +271,21 @@ def qh_sync_user(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):
objs = ProjectMembership.objects.select_related('person')
memberships = objs.filter(project=project,
......@@ -316,24 +298,14 @@ def qh_sync_project(project):
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):
users = AstakosUser.objects.filter(
moderated=True, is_rejected=False).order_by('id').select_for_update()
resource_name = resource.name
limit = resource.uplimit
data = []
entries = []
for user in users:
uuid = user.uuid
key = uuid, SYSTEM, resource_name
data.append((key, limit))
qh.set_quota(data)
entries.append(
AstakosUserQuota(user=user, resource=resource,
capacity=resource.uplimit))
AstakosUserQuota.objects.bulk_create(entries)
qh_sync_users(users)
......@@ -104,19 +104,19 @@ def add_resource(resource_dict):
return r, exists
def update_resource(resource, uplimit):
old_uplimit = resource.uplimit
if uplimit == old_uplimit:
logger.info("Resource %s has limit %s; no need to update."
% (resource.name, uplimit))
return []
else:
resource.uplimit = uplimit
resource.save()
logger.info("Updated resource %s with limit %s."
% (resource.name, uplimit))
affected = quotas.qh_change_resource_limit(resource)
return affected
def update_resources(updates):
resources = []
for resource, uplimit in updates:
resources.append(resource)
old_uplimit = resource.uplimit
if uplimit == old_uplimit:
logger.info("Resource %s has limit %s; no need to update."
% (resource.name, uplimit))
else:
resource.uplimit = uplimit
resource.save()
logger.info("Updated resource %s with limit %s."
% (resource.name, uplimit))
def get_resources(resources=None, services=None):
......
......@@ -64,14 +64,14 @@ class QuotaAPITest(TestCase):
"service_origin": "service1",
"allow_in_projects": True}
r, _ = register.add_resource(resource11)
register.update_resource(r, 100)
register.update_resources([(r, 100)])
resource12 = {"name": "service1.resource12",
"desc": "resource11 desc",
"service_type": "type1",
"service_origin": "service1",
"unit": "bytes"}
r, _ = register.add_resource(resource12)
register.update_resource(r, 1024)
register.update_resources([(r, 1024)])
# create user
user = get_local_user('test@grnet.gr')
......@@ -91,7 +91,7 @@ class QuotaAPITest(TestCase):
"service_origin": "service2",
"allow_in_projects": False}
r, _ = register.add_resource(resource21)
register.update_resource(r, 3)
register.update_resources([(r, 3)])
resource_names = [r['name'] for r in
[resource11, resource12, resource21]]
......
......@@ -48,14 +48,14 @@ class ProjectAPITest(TestCase):
"service_origin": "service1",
"allow_in_projects": True}
r, _ = register.add_resource(resource11)
register.update_resource(r, 100)
register.update_resources([(r, 100)])
resource12 = {"name": "service1.resource12",
"desc": "resource11 desc",
"service_type": "type1",
"service_origin": "service1",
"unit": "bytes"}
r, _ = register.add_resource(resource12)
register.update_resource(r, 1024)
register.update_resources([(r, 1024)])
# create user
self.user1 = get_local_user("test@grnet.gr", moderated=True)
......@@ -76,7 +76,7 @@ class ProjectAPITest(TestCase):
"service_origin": "astakos_account",
"allow_in_projects": False}
r, _ = register.add_resource(pending_app)
register.update_resource(r, 3)
register.update_resources([(r, 3)])
def create(self, app, headers):
dump = json.dumps(app)
......@@ -618,7 +618,7 @@ class TestProjects(TestCase):
self.member_client = get_user_client("member@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):
Service.objects.all().delete()
......@@ -671,7 +671,8 @@ class TestProjects(TestCase):
@im_settings(PROJECT_ADMINS=['uuid1'])
def test_applications(self):
# 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)
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