Commit 545ef538 authored by Giorgos Korfiatis's avatar Giorgos Korfiatis Committed by Christos Stavrakakis
Browse files

astakos: Enhance quota listing

Enable filtering quota by resource, over limit status, usage and
limit values.
parent d1f7ab64
......@@ -319,6 +319,11 @@ you can set it for each resource like this::
# snf-manage user-modify 'user-uuid' --set-base-quota 'cyclades.vm' 10
All quota for which values different from the default have been set,
can be listed with::
# snf-manage quota-list --with-custom=True
Enable the Projects feature
~~~~~~~~~~~~~~~~~~~~~~~~~~~
......
......@@ -197,27 +197,51 @@ def show_resource_value(number, resource, style):
return units.show(number, unit, style)
def collect_holder_quotas(holder_quotas, h_initial, style=None):
print_data = []
for source, source_quotas in holder_quotas.iteritems():
try:
s_initial = h_initial[source]
except KeyError:
continue
for resource, values in source_quotas.iteritems():
try:
initial = s_initial[resource]
except KeyError:
continue
initial = show_resource_value(initial, resource, style)
limit = show_resource_value(values['limit'], resource, style)
usage = show_resource_value(values['usage'], resource, style)
fields = (source, resource, initial, limit, usage)
print_data.append(fields)
return print_data
def show_user_quotas(holder_quotas, h_initial, style=None):
labels = ('source', 'resource', 'base_quota', 'total_quota', 'usage')
print_data = collect_holder_quotas(holder_quotas, h_initial, style=style)
return print_data, labels
def show_quotas(qh_quotas, astakos_initial, info=None, style=None):
labels = ('source', 'resource', 'base quota', 'total quota', 'usage')
labels = ('user', 'source', 'resource', 'base_quota', 'total_quota',
'usage')
if info is not None:
labels = ('uuid', 'email') + labels
labels = ('displayname',) + labels
print_data = []
for holder, holder_quotas in qh_quotas.iteritems():
h_initial = astakos_initial.get(holder)
if h_initial is None:
continue
if info is not None:
email = info.get(holder, "")
for source, source_quotas in holder_quotas.iteritems():
s_initial = h_initial.get(source) if h_initial else None
for resource, values in source_quotas.iteritems():
initial = s_initial.get(resource) if s_initial else None
initial = show_resource_value(initial, resource, style)
limit = show_resource_value(values['limit'], resource, style)
usage = show_resource_value(values['usage'], resource, style)
fields = (source, resource, initial, limit, usage)
if info is not None:
fields = (holder, email) + fields
print_data.append(fields)
h_data = collect_holder_quotas(holder_quotas, h_initial, style=style)
if info is not None:
h_data = [(email, holder) + fields for fields in h_data]
else:
h_data = [(holder,) + fields for fields in h_data]
print_data += h_data
return print_data, labels
# Copyright 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.
from synnefo.util import units
from django.core.management import CommandError
from django.db.models import Q
OP_MAP = [
("!=", lambda x: ~x, ""),
(">=", lambda x: x, "__gte"),
("=>", lambda x: x, "__gte"),
(">", lambda x: x, "__gt"),
("<=", lambda x: x, "__lte"),
("=<", lambda x: x, "__lte"),
("<", lambda x: x, "__lt"),
("=", lambda x: x, ""),
]
def parse_filter(exp):
for s, prepend, op in OP_MAP:
key, sep, value = exp.partition(s)
if s == sep:
return key, prepend, op, value
raise CommandError("Could not parse filter.")
def make_query(flt, handlers):
key, prepend, opstr, value = parse_filter(flt)
try:
(dbkey, parse) = handlers[key]
return prepend(Q(**{dbkey+opstr: parse(value)}))
except KeyError:
return None
def parse_with_unit(value):
try:
return units.parse(value)
except units.ParseError:
raise CommandError("Failed to parse value, should be an integer, "
"possibly followed by a unit, or 'inf'.")
......@@ -36,9 +36,11 @@ from django.db import transaction
from astakos.im.models import AstakosUser
from astakos.im.quotas import list_user_quotas
from snf_django.management.commands import SynnefoCommand
from snf_django.management.commands import SynnefoCommand, CommandError
from snf_django.management import utils
from astakos.im.management.commands import _common as common
from astakos.im.management.commands import _filtering as filtering
from django.db.models import Q, F
import logging
logger = logging.getLogger(__name__)
......@@ -53,29 +55,77 @@ class Command(SynnefoCommand):
help=("Specify display unit for resource values "
"(one of %s); defaults to mb") %
common.style_options),
make_option('--user',
metavar='<uuid or email>',
dest='user',
help="List quota for a specified user"),
make_option('--overlimit',
action='store_true',
help="Show quota that is over limit"),
make_option('--with-custom',
metavar='True|False',
help=("Filter quota different from the default or "
"equal to it")),
make_option('--filter-by',
help="Filter by field; "
"e.g. \"user=uuid,usage>=10M,base_quota<inf\""),
make_option('--displayname',
action='store_true',
help="Show user display name"),
)
QHFLT = {
"total_quota": ("limit", filtering.parse_with_unit),
"usage": ("usage_max", filtering.parse_with_unit),
"user": ("holder", lambda x: x),
"resource": ("resource", lambda x: x),
"source": ("source", lambda x: x),
}
INITFLT = {
"base_quota": ("capacity", filtering.parse_with_unit),
}
@transaction.commit_on_success
def handle(self, *args, **options):
output_format = options["output_format"]
user_ident = options['user']
displayname = bool(options["displayname"])
unit_style = options["unit_style"]
common.check_style(unit_style)
if user_ident is not None:
users = [common.get_accepted_user(user_ident)]
filteropt = options["filter_by"]
if filteropt is not None:
filters = filteropt.split(",")
else:
users = AstakosUser.objects.accepted()
filters = []
QHQ, INITQ = Q(), Q()
for flt in filters:
q = filtering.make_query(flt, self.QHFLT)
if q is not None:
QHQ &= q
q = filtering.make_query(flt, self.INITFLT)
if q is not None:
INITQ &= q
qh_quotas, astakos_i = list_user_quotas(users)
overlimit = bool(options["overlimit"])
if overlimit:
QHQ &= Q(usage_max__gt=F("limit"))
info = {}
for user in users:
info[user.uuid] = user.email
with_custom = options["with_custom"]
if with_custom is not None:
qeq = Q(capacity=F("resource__uplimit"))
try:
INITQ &= ~qeq if utils.parse_bool(with_custom) else qeq
except ValueError as e:
raise CommandError(e)
users = AstakosUser.objects.accepted()
qh_quotas, astakos_i = list_user_quotas(
users, qhflt=QHQ, initflt=INITQ)
if displayname:
info = {}
for user in users:
info[user.uuid] = user.email
else:
info = None
print_data, labels = common.show_quotas(
qh_quotas, astakos_i, info, style=unit_style)
......
......@@ -42,7 +42,7 @@ from synnefo.lib.ordereddict import OrderedDict
from snf_django.management.commands import SynnefoCommand
from snf_django.management import utils
from ._common import show_quotas, style_options, check_style
from ._common import show_user_quotas, style_options, check_style
import uuid
......@@ -128,10 +128,12 @@ class Command(SynnefoCommand):
check_style(unit_style)
quotas, initial = list_user_quotas([user])
h_quotas = quotas[user.uuid]
h_initial = initial[user.uuid]
if quotas:
self.stdout.write("\n")
print_data, labels = show_quotas(quotas, initial,
style=unit_style)
print_data, labels = show_user_quotas(h_quotas, h_initial,
style=unit_style)
utils.pprint_table(self.stdout, print_data, labels,
options["output_format"],
title="User Quota")
......
......@@ -31,7 +31,6 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
import copy
from synnefo.util import units
from astakos.im.models import (
Resource, AstakosUserQuota, AstakosUser, Service,
......@@ -70,17 +69,18 @@ def transform_data(holdings, func=None):
return quota
def get_counters(users, resources=None, sources=None):
def get_counters(users, resources=None, sources=None, flt=None):
uuids = [user.uuid for user in users]
counters = qh.get_quota(holders=uuids,
resources=resources,
sources=sources)
sources=sources,
flt=flt)
return counters
def get_users_quotas(users, resources=None, sources=None):
counters = get_counters(users, resources, sources)
def get_users_quotas(users, resources=None, sources=None, flt=None):
counters = get_counters(users, resources, sources, flt=flt)
quotas = transform_data(counters)
return quotas
......@@ -149,10 +149,14 @@ def update_base_quota(quota, capacity):
qh_sync_locked_user(quota.user)
def initial_quotas(users):
def initial_quotas(users, flt=None):
if flt is None:
flt = Q()
userids = [user.pk for user in users]
objs = AstakosUserQuota.objects.select_related()
orig_quotas = objs.filter(user__pk__in=userids)
orig_quotas = objs.filter(user__pk__in=userids).filter(flt)
initial = {}
for user_quota in orig_quotas:
uuid = user_quota.user.uuid
......@@ -216,9 +220,9 @@ def astakos_users_quotas(users):
return quotas
def list_user_quotas(users):
qh_quotas = get_users_quotas(users)
astakos_initial = initial_quotas(users)
def list_user_quotas(users, qhflt=None, initflt=None):
qh_quotas = get_users_quotas(users, flt=qhflt)
astakos_initial = initial_quotas(users, flt=initflt)
return qh_quotas, astakos_initial
......
......@@ -32,7 +32,7 @@
# or implied, of GRNET S.A.
from datetime import datetime
from django.db.models import F
from django.db.models import Q
from astakos.quotaholder_app.exception import (
QuotaholderError,
NoCommissionError,
......@@ -51,8 +51,11 @@ def format_datetime(d):
return d.strftime('%Y-%m-%dT%H:%M:%S.%f')[:24]
def get_quota(holders=None, sources=None, resources=None):
holdings = Holding.objects.all()
def get_quota(holders=None, sources=None, resources=None, flt=None):
if flt is None:
flt = Q()
holdings = Holding.objects.filter(flt)
if holders is not None:
holdings = holdings.filter(holder__in=holders)
......
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