Commit fc5e138f authored by Giorgos Korfiatis's avatar Giorgos Korfiatis
Browse files

astakos: Remove command user-set-initial-quota

To change base quota for a specified user:
  snf-manage user-modify user --set-base-quota resource limit

To import base quota in batch from a file:
  snf-manage astakos-quota --import-base-quota filename

Rename `initial' to `base quota' in command output.
Move code that creates/removes AstakosUserQuota entries in file quotas.py;
sync user to quotaholder after such a change.

Refs #3411
parent 8be5855c
......@@ -160,12 +160,9 @@ For individual users that need different quotas than the default
you can set it for each resource like this::
# use this to display quotas / uuid
# snf-manage user-show 'uuid or email'
# snf-manage user-show 'uuid or email' --quotas
# snf-manage user-set-initial-quota --set-capacity 'user-uuid' 'cyclades.vm' 10
# this applies the configuration
# snf-manage astakos-quota --sync --user 'user-uuid'
# snf-manage user-modify 'user-uuid' --set-quota 'cyclades.vm' 10
Enable the Projects feature
......@@ -185,7 +182,7 @@ per user with::
You can also set a user-specific limit with::
# snf-manage user-set-initial-quota --set-capacity 'user-uuid' 'astakos.pending_app' 5
# snf-manage user-modify 'user-uuid' --set-quota 'astakos.pending_app' 5
When users apply for projects they are not automatically granted
the resources. They must first be approved by the administrator.
......
......@@ -197,6 +197,5 @@ user-group-list List available groups
user-invite Invite somebody
user-list List users
user-modify Modify user
user-set-initial-quota Import user quota limits from file or set quota for a single user from the command line
user-show Show user details
============================ ===========================
......@@ -237,7 +237,7 @@ def is_email(s):
def show_quotas(qh_quotas, astakos_initial, info=None):
labels = ('source', 'resource', 'initial', 'total', 'usage')
labels = ('source', 'resource', 'base quota', 'total quota', 'usage')
if info is not None:
labels = ('uuid', 'email') + labels
......
......@@ -35,7 +35,7 @@ from optparse import make_option
from django.core.management.base import CommandError
from astakos.im.models import AstakosUser
from astakos.im.quotas import set_user_quota, list_user_quotas
from astakos.im.quotas import set_user_quota, list_user_quotas, add_base_quota
from astakos.im.functions import get_user_by_uuid
from astakos.im.management.commands._common import is_uuid, is_email
from snf_django.lib.db.transaction import commit_on_success_strict
......@@ -70,6 +70,14 @@ class Command(SynnefoCommand):
metavar='<uuid or email>',
dest='user',
help="List quotas for a specified user"),
make_option('--import-base-quota',
dest='import_base_quota',
metavar='<exported-quotas.txt>',
help=("Import base quotas from file. "
"The file must contain non-empty lines, and each "
"line must contain a single-space-separated list "
"of values: <user> <resource name> <capacity>")
),
)
@commit_on_success_strict()
......@@ -77,6 +85,18 @@ class Command(SynnefoCommand):
sync = options['sync']
verify = options['verify']
user_ident = options['user']
list_ = options['list']
import_base_quota = options['import_base_quota']
if import_base_quota:
if any([sync, verify, list_]):
m = "--from-file cannot be combined with other options."
raise CommandError(m)
self.import_from_file(import_base_quota)
else:
self.quotas(sync, verify, user_ident, options["output_format"])
def quotas(self, sync, verify, user_ident, output_format):
list_only = not sync and not verify
if user_ident is not None:
......@@ -97,7 +117,7 @@ class Command(SynnefoCommand):
if list_only:
print_data, labels = show_quotas(qh_quotas, astakos_i, info)
utils.pprint_table(self.stdout, print_data, labels,
options["output_format"])
output_format)
else:
if verify:
......@@ -163,3 +183,29 @@ class Command(SynnefoCommand):
diffs = len(diff_quotas)
if diffs:
self.stdout.write("Quotas differ for %d users.\n" % (diffs))
def import_from_file(self, location):
users = set()
with open(location) as f:
for line in f.readlines():
try:
t = line.rstrip('\n').split(' ')
user = t[0]
resource = t[1]
capacity = t[2]
except(IndexError, TypeError):
self.stdout.write('Invalid line format: %s:\n' % t)
continue
else:
try:
user = self.get_user(user)
users.add(user.id)
except CommandError:
self.stdout.write('Not found user: %s\n' % user)
continue
else:
try:
add_base_quota(user, resource, capacity)
except Exception, e:
self.stdout.write('Failed to add quota: %s\n' % e)
continue
......@@ -39,8 +39,10 @@ from django.core.exceptions import ValidationError
from astakos.im.models import AstakosUser
from astakos.im.functions import (activate, deactivate)
from astakos.im import quotas
from ._common import remove_user_permission, add_user_permission, is_uuid
from snf_django.lib.db.transaction import commit_on_success_strict
import string
class Command(BaseCommand):
......@@ -102,6 +104,15 @@ class Command(BaseCommand):
make_option('--delete-permission',
dest='delete-permission',
help="Delete user permission"),
make_option('--set-base-quota',
dest='set_base_quota',
metavar='<resource> <capacity>',
nargs=2,
help=("Set base quota for a specified resource. "
"The special value 'default' sets the user base "
"quota to the default value.")
),
)
@commit_on_success_strict()
......@@ -210,3 +221,47 @@ class Command(BaseCommand):
if password:
self.stdout.write('User\'s new password: %s\n' % password)
set_base_quota = options.get('set_base_quota')
if set_base_quota is not None:
resource, capacity = set_base_quota
self.set_limit(user, resource, capacity, False)
def set_limit(self, user, resource, capacity, force):
if capacity != 'default':
try:
capacity = int(capacity)
except ValueError:
m = "Please specify capacity as a decimal integer or 'default'"
raise CommandError(m)
try:
quota, default_capacity = user.get_resource_policy(resource)
except Resource.DoesNotExist:
raise CommandError("No such resource: %s" % resource)
current = quota.capacity if quota is not None else 'default'
if not force:
self.stdout.write("user: %s (%s)\n" % (user.uuid, user.username))
self.stdout.write("default capacity: %s\n" % default_capacity)
self.stdout.write("current capacity: %s\n" % current)
self.stdout.write("new capacity: %s\n" % capacity)
self.stdout.write("Confirm? (y/n) ")
response = raw_input()
if string.lower(response) not in ['y', 'yes']:
self.stdout.write("Aborted.\n")
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)
# Copyright 2012, 2013 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.
import string
from optparse import make_option
from collections import namedtuple
from django.core.management.base import BaseCommand, CommandError
from snf_django.lib.db.transaction import commit_on_success_strict
from astakos.im.models import AstakosUser, AstakosUserQuota, Resource
from astakos.im.quotas import qh_sync_user, qh_sync_users
from ._common import is_uuid, is_email
AddResourceArgs = namedtuple('AddQuotaArgs', ('resource',
'capacity',
))
class Command(BaseCommand):
help = """Import user quota limits from file or set quota
for a single user from the command line
The file must contain non-empty lines, and each line must
contain a single-space-separated list of values:
<user> <resource name> <capacity>
For example to grant the following user with 10 private networks
(independent of any he receives from projects):
6119a50b-cbc7-42c0-bafc-4b6570e3f6ac cyclades.network.private 10
Similar syntax is used when setting quota from the command line:
--set-capacity 6119a50b-cbc7-42c0-bafc-4b6570e3f6ac cyclades.vm 10
The special value of 'default' sets the user setting to the default.
"""
option_list = BaseCommand.option_list + (
make_option('--from-file',
dest='from_file',
metavar='<exported-quotas.txt>',
help="Import quotas from file"),
make_option('--set-capacity',
dest='set_capacity',
metavar='<uuid or email> <resource> <capacity>',
nargs=3,
help="Set capacity for a specified user/resource pair"),
make_option('-f', '--no-confirm',
action='store_true',
default=False,
dest='force',
help="Do not ask for confirmation"),
)
@commit_on_success_strict()
def handle(self, *args, **options):
from_file = options['from_file']
set_capacity = options['set_capacity']
force = options['force']
if from_file is not None:
if set_capacity is not None:
raise CommandError("Cannot combine option `--from-file' with "
"`--set-capacity'.")
self.import_from_file(from_file)
return
if set_capacity is not None:
user, resource, capacity = set_capacity
self.set_limit(user, resource, capacity, force)
return
m = "Please use either `--from-file' or `--set-capacity' options"
raise CommandError(m)
def set_limit(self, user_ident, resource, capacity, force):
if is_uuid(user_ident):
try:
user = AstakosUser.objects.get(uuid=user_ident)
except AstakosUser.DoesNotExist:
raise CommandError('Not found user having uuid: %s' %
user_ident)
elif is_email(user_ident):
try:
user = AstakosUser.objects.get(username=user_ident)
except AstakosUser.DoesNotExist:
raise CommandError('Not found user having email: %s' %
user_ident)
else:
raise CommandError('Please specify user by uuid or email')
if capacity != 'default':
try:
capacity = int(capacity)
except ValueError:
m = "Please specify capacity as a decimal integer or 'default'"
raise CommandError(m)
args = AddResourceArgs(resource=resource,
capacity=capacity,
)
try:
quota, default_capacity = user.get_resource_policy(resource)
except Resource.DoesNotExist:
raise CommandError("No such resource: %s" % resource)
current = quota.capacity if quota is not None else 'default'
if not force:
self.stdout.write("user: %s (%s)\n" % (user.uuid, user.username))
self.stdout.write("default capacity: %s\n" % default_capacity)
self.stdout.write("current capacity: %s\n" % current)
self.stdout.write("new capacity: %s\n" % capacity)
self.stdout.write("Confirm? (y/n) ")
response = raw_input()
if string.lower(response) not in ['y', 'yes']:
self.stdout.write("Aborted.\n")
return
if capacity == 'default':
try:
q = AstakosUserQuota.objects.get(user=user,
resource__name=resource)
q.delete()
except Exception as e:
import traceback
traceback.print_exc()
raise CommandError("Failed to remove policy: %s" % e)
else:
try:
user.add_resource_policy(*args)
except Exception as e:
raise CommandError("Failed to add policy: %s" % e)
qh_sync_user(user.id)
def import_from_file(self, location):
users = set()
with open(location) as f:
for line in f.readlines():
try:
t = line.rstrip('\n').split(' ')
user = t[0]
args = AddResourceArgs(*t[1:])
except(IndexError, TypeError):
self.stdout.write('Invalid line format: %s:\n' % t)
continue
else:
try:
user = AstakosUser.objects.get(uuid=user)
users.add(user.id)
except AstakosUser.DoesNotExist:
self.stdout.write('Not found user having uuid: %s\n'
% user)
continue
else:
try:
user.add_resource_policy(*args)
except Exception, e:
self.stdout.write('Failed to policy: %s\n' % e)
continue
qh_sync_users(users)
......@@ -446,30 +446,6 @@ class AstakosUser(User):
def policies(self):
return self.astakosuserquota_set.select_related().all()
@policies.setter
def policies(self, policies):
for p in policies:
p.setdefault('resource', '')
p.setdefault('capacity', 0)
p.setdefault('update', True)
self.add_resource_policy(**p)
def add_resource_policy(
self, resource, capacity,
update=True):
"""Raises ObjectDoesNotExist, IntegrityError"""
resource = Resource.objects.get(name=resource)
if update:
AstakosUserQuota.objects.update_or_create(
user=self, resource=resource, defaults={
'capacity':capacity,
})
else:
q = self.astakosuserquota_set
q.create(
resource=resource, capacity=capacity,
)
def get_resource_policy(self, resource):
resource = Resource.objects.get(name=resource)
default_capacity = resource.uplimit
......@@ -479,11 +455,6 @@ class AstakosUser(User):
except AstakosUserQuota.DoesNotExist:
return None, default_capacity
def remove_resource_policy(self, service, resource):
"""Raises ObjectDoesNotExist, IntegrityError"""
resource = Resource.objects.get(name=resource)
q = self.policies.get(resource=resource).delete()
def update_uuid(self):
while not self.uuid:
uuid_val = str(uuid.uuid4())
......
......@@ -152,6 +152,25 @@ def register_pending_apps(user, quantity, force=False, dry_run=False):
return True, None
def add_base_quota(user, resource, capacity):
resource = Resource.objects.get(name=resource)
obj, created = AstakosUserQuota.objects.get_or_create(
user=user, resource=resource, defaults={
'capacity': capacity,
})
if not created:
obj.capacity = capacity
obj.save()
qh_sync_user(user.id)
def remove_base_quota(user, resource):
AstakosUserQuota.objects.filter(
user=user, resource__name=resource).delete()
qh_sync_user(user.id)
def initial_quotas(users):
initial = {}
default_quotas = get_default_quota()
......
......@@ -1274,8 +1274,7 @@ class TestProjects(TestCase):
@im_settings(PROJECT_ADMINS=['uuid1'])
def test_applications(self):
# let user have 2 pending applications
self.user.add_resource_policy('astakos.pending_app', 2)
quotas.qh_sync_users(AstakosUser.objects.all())
quotas.add_base_quota(self.user, 'astakos.pending_app', 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