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

astakos: Lift old-style services to components

Add model Component and migrate services along with their token
and UI URL to this new model. Rename service to component where
needed.

Add field service_type in Resource and fill it based on the name
of the service that used to own the resource.
parent 149f9c6b
......@@ -47,7 +47,7 @@ import astakos.quotaholder_app.exception as qh_exception
import astakos.quotaholder_app.callpoint as qh
from .util import (json_response, is_integer, are_integer,
user_from_token, service_from_token)
user_from_token, component_from_token)
@api.api_method(http_method='GET', token_required=True, user_required=False)
@user_from_token
......@@ -57,11 +57,11 @@ def quotas(request):
@api.api_method(http_method='GET', token_required=True, user_required=False)
@service_from_token
@component_from_token
def service_quotas(request):
user = request.GET.get('user')
users = [user] if user is not None else None
result = service_get_quotas(request.service_instance, users=users)
result = service_get_quotas(request.component_instance, users=users)
if user is not None and result == {}:
raise ItemNotFound("No such user '%s'" % user)
......@@ -87,10 +87,10 @@ def commissions(request):
@api.api_method(http_method='GET', token_required=True, user_required=False)
@service_from_token
@component_from_token
def get_pending_commissions(request):
data = request.GET
client_key = str(request.service_instance)
client_key = str(request.component_instance)
result = qh.get_pending_commissions(clientkey=client_key)
return json_response(result)
......@@ -115,7 +115,7 @@ def _provisions_to_list(provisions):
@csrf_exempt
@api.api_method(http_method='POST', token_required=True, user_required=False)
@service_from_token
@component_from_token
def issue_commission(request):
data = request.raw_post_data
try:
......@@ -123,7 +123,7 @@ def issue_commission(request):
except json.JSONDecodeError:
raise BadRequest("POST data should be in json format.")
client_key = str(request.service_instance)
client_key = str(request.component_instance)
provisions = input_data.get('provisions')
if provisions is None:
raise BadRequest("Provisions are missing.")
......@@ -205,7 +205,7 @@ def conflictingCF(serial):
@csrf_exempt
@api.api_method(http_method='POST', token_required=True, user_required=False)
@service_from_token
@component_from_token
@commit_on_success_strict()
def resolve_pending_commissions(request):
data = request.raw_post_data
......@@ -214,7 +214,7 @@ def resolve_pending_commissions(request):
except json.JSONDecodeError:
raise BadRequest("POST data should be in json format.")
client_key = str(request.service_instance)
client_key = str(request.component_instance)
accept = input_data.get('accept', [])
reject = input_data.get('reject', [])
......@@ -241,10 +241,10 @@ def resolve_pending_commissions(request):
@api.api_method(http_method='GET', token_required=True, user_required=False)
@service_from_token
@component_from_token
def get_commission(request, serial):
data = request.GET
client_key = str(request.service_instance)
client_key = str(request.component_instance)
try:
serial = int(serial)
except ValueError:
......@@ -261,7 +261,7 @@ def get_commission(request, serial):
@csrf_exempt
@api.api_method(http_method='POST', token_required=True, user_required=False)
@service_from_token
@component_from_token
@commit_on_success_strict()
def serial_action(request, serial):
data = request.raw_post_data
......@@ -275,7 +275,7 @@ def serial_action(request, serial):
except ValueError:
raise BadRequest("Serial should be an integer.")
client_key = str(request.service_instance)
client_key = str(request.component_instance)
accept = 'accept' in input_data
reject = 'reject' in input_data
......
......@@ -38,7 +38,7 @@ from snf_django.lib import api
from .util import (
get_uuid_displayname_catalogs as get_uuid_displayname_catalogs_util,
send_feedback as send_feedback_util,
service_from_token)
component_from_token)
import logging
logger = logging.getLogger(__name__)
......@@ -47,7 +47,7 @@ logger = logging.getLogger(__name__)
@csrf_exempt
@api.api_method(http_method='POST', token_required=True, user_required=False,
logger=logger)
@service_from_token # Authenticate service !!
@component_from_token # Authenticate service !!
def get_uuid_displayname_catalogs(request):
# Normal Response Codes: 200
# Error Response Codes: internalServerError (500)
......@@ -59,7 +59,7 @@ def get_uuid_displayname_catalogs(request):
@csrf_exempt
@api.api_method(http_method='POST', token_required=True, user_required=False,
logger=logger)
@service_from_token # Authenticate service !!
@component_from_token # Authenticate service !!
def send_feedback(request, email_template_name='im/feedback_mail.txt'):
# Normal Response Codes: 200
# Error Response Codes: internalServerError (500)
......
......@@ -39,7 +39,7 @@ from django.http import HttpResponse
from django.utils import simplejson as json
from django.template.loader import render_to_string
from astakos.im.models import AstakosUser, Service
from astakos.im.models import AstakosUser, Component
from snf_django.lib.api import faults
from snf_django.lib.api.utils import isoformat
......@@ -126,11 +126,11 @@ def user_from_token(func):
return wrapper
def service_from_token(func):
"""Decorator for authenticating service by it's token.
def component_from_token(func):
"""Decorator for authenticating component by its token.
Check that a service with the corresponding token exists. Also,
if service's token has an expiration token, check that it has not
Check that a component with the corresponding token exists. Also,
if component's token has an expiration token, check that it has not
expired.
"""
......@@ -144,18 +144,18 @@ def service_from_token(func):
if not token:
raise faults.Unauthorized("Invalid X-Auth-Token")
try:
service = Service.objects.get(auth_token=token)
except Service.DoesNotExist:
component = Component.objects.get(auth_token=token)
except Component.DoesNotExist:
raise faults.Unauthorized("Invalid X-Auth-Token")
# Check if the token has expired
expiration_date = service.auth_token_expires
expiration_date = component.auth_token_expires
if expiration_date:
expires_at = mktime(expiration_date.timetuple())
if time() > expires_at:
raise faults.Unauthorized("Authentication expired")
request.service_instance = service
request.component_instance = component
return func(request, *args, **kwargs)
return wrapper
......
This diff is collapsed.
......@@ -93,13 +93,11 @@ def get_content_type():
inf = float('inf')
class Service(models.Model):
class Component(models.Model):
name = models.CharField(_('Name'), max_length=255, unique=True,
db_index=True)
url = models.CharField(_('Service url'), max_length=255, null=True,
help_text=_("URL the service is accessible from"))
api_url = models.CharField(_('Service API url'), max_length=255, null=True)
type = models.CharField(_('Type'), max_length=255, null=True, blank='True')
url = models.CharField(_('Component url'), max_length=255, null=True,
help_text=_("URL the component is accessible from"))
auth_token = models.CharField(_('Authentication Token'), max_length=32,
null=True, blank=True)
auth_token_created = models.DateTimeField(_('Token creation date'),
......@@ -110,7 +108,7 @@ class Service(models.Model):
def renew_token(self, expiration_date=None):
md5 = hashlib.md5()
md5.update(self.name.encode('ascii', 'ignore'))
md5.update(self.api_url.encode('ascii', 'ignore'))
md5.update(self.url.encode('ascii', 'ignore'))
md5.update(asctime())
self.auth_token = b64encode(md5.digest())
......@@ -126,41 +124,40 @@ class Service(models.Model):
@classmethod
def catalog(cls, orderfor=None):
catalog = {}
services = list(cls.objects.all())
default_metadata = presentation.SERVICES
components = list(cls.objects.all())
default_metadata = presentation.COMPONENTS
metadata = {}
for service in services:
d = {'api_url': service.api_url,
'url': service.url,
'name': service.name}
if service.name in default_metadata:
metadata[service.name] = default_metadata.get(service.name)
metadata[service.name].update(d)
for component in components:
d = {'url': component.url,
'name': component.name}
if component.name in default_metadata:
metadata[component.name] = default_metadata.get(component.name)
metadata[component.name].update(d)
else:
metadata[service.name] = d
metadata[component.name] = d
def service_by_order(s):
def component_by_order(s):
return s[1].get('order')
def service_by_dashbaord_order(s):
def component_by_dashboard_order(s):
return s[1].get('dashboard').get('order')
metadata = dict_merge(metadata,
astakos_settings.SERVICES_META)
astakos_settings.COMPONENTS_META)
for service, info in metadata.iteritems():
default_meta = presentation.service_defaults(service)
base_meta = metadata.get(service, {})
settings_meta = astakos_settings.SERVICES_META.get(service, {})
service_meta = dict_merge(default_meta, base_meta)
meta = dict_merge(service_meta, settings_meta)
catalog[service] = meta
for component, info in metadata.iteritems():
default_meta = presentation.component_defaults(component)
base_meta = metadata.get(component, {})
settings_meta = astakos_settings.COMPONENTS_META.get(component, {})
component_meta = dict_merge(default_meta, base_meta)
meta = dict_merge(component_meta, settings_meta)
catalog[component] = meta
order_key = service_by_order
order_key = component_by_order
if orderfor == 'dashboard':
order_key = service_by_dashbaord_order
order_key = component_by_dashboard_order
ordered_catalog = OrderedDict(sorted(catalog.iteritems(),
key=order_key))
......@@ -180,10 +177,26 @@ def get_presentation(resource):
return resource_presentation
class Service(models.Model):
name = models.CharField(_('Name'), max_length=255, unique=True,
db_index=True)
url = models.CharField(_('Service url'), max_length=255, null=True,
help_text=_("URL the service is accessible from"))
api_url = models.CharField(_('Service API url'), max_length=255, null=True)
type = models.CharField(_('Type'), max_length=255, null=True, blank='True')
auth_token = models.CharField(_('Authentication Token'), max_length=32,
null=True, blank=True)
auth_token_created = models.DateTimeField(_('Token creation date'),
null=True)
auth_token_expires = models.DateTimeField(_('Token expiration date'),
null=True)
class Resource(models.Model):
name = models.CharField(_('Name'), max_length=255, unique=True)
desc = models.TextField(_('Description'), null=True)
service = models.ForeignKey(Service)
service_type = models.CharField(_('Type'), max_length=255)
unit = models.CharField(_('Unit'), null=True, max_length=255)
uplimit = intDecimalField(default=0)
allow_in_projects = models.BooleanField(default=True)
......@@ -197,7 +210,7 @@ class Resource(models.Model):
return str(self)
def get_info(self):
return {'service': str(self.service),
return {'service_type': self.service_type,
'description': self.desc,
'unit': self.unit,
'allow_in_projects': self.allow_in_projects,
......@@ -2172,4 +2185,4 @@ def renew_token(sender, instance, **kwargs):
if not instance.auth_token:
instance.renew_token()
pre_save.connect(renew_token, sender=AstakosUser)
pre_save.connect(renew_token, sender=Service)
pre_save.connect(renew_token, sender=Component)
......@@ -162,7 +162,7 @@ RESOURCES = {
RESOURCES = dict_merge(RESOURCES, settings.RESOURCES_META)
def service_defaults(service_name):
def component_defaults(service_name):
"""
Metadata for unkown services
"""
......@@ -182,7 +182,7 @@ def service_defaults(service_name):
}
SERVICES = {
COMPONENTS = {
'astakos': {
'url': '/im/landing',
'order': 1,
......
......@@ -33,7 +33,7 @@
import copy
from astakos.im.models import (
Resource, AstakosUserQuota, AstakosUser,
Resource, AstakosUserQuota, AstakosUser, Service,
Project, ProjectMembership, ProjectResourceGrant, ProjectApplication)
import astakos.quotaholder_app.callpoint as qh
from astakos.quotaholder_app.exception import NoCapacityError
......@@ -95,8 +95,11 @@ def get_user_quotas(user, resources=None, sources=None):
return quotas.get(user.uuid, {})
def service_get_quotas(service, users=None):
resources = Resource.objects.filter(service=service)
def service_get_quotas(component, users=None):
type_values = Service.objects.filter(
component=component).values_list('type')
service_types = [t for (t,) in type_values]
resources = Resource.objects.filter(service_type__in=service_types)
resource_names = [r.name for r in resources]
counters = qh.get_quota(holders=users, resources=resource_names)
return transform_data(counters)
......
......@@ -170,8 +170,8 @@ LOGIN_SUCCESS_URL = getattr(settings, 'ASTAKOS_LOGIN_SUCCESS_URL',
# Whether or not to display projects in astakos menu
PROJECTS_VISIBLE = getattr(settings, 'ASTAKOS_PROJECTS_VISIBLE', False)
# A way to extend the services presentation metadata
SERVICES_META = getattr(settings, 'ASTAKOS_SERVICES_META', {})
# A way to extend the components presentation metadata
COMPONENTS_META = getattr(settings, 'ASTAKOS_COMPONENTS_META', {})
# A way to extend the resources presentation metadata
RESOURCES_META = getattr(settings, 'ASTAKOS_RESOURCES_META', {})
......
......@@ -55,7 +55,7 @@ import astakos.im.messages as astakos_messages
from astakos.im import activation_backends
from astakos.im.models import AstakosUser, ApprovalTerms, EmailChange, \
AstakosUserAuthProvider, PendingThirdPartyUser, Service
AstakosUserAuthProvider, PendingThirdPartyUser, Component
from astakos.im.util import get_context, prepare_response, get_query, \
restrict_next
from astakos.im.forms import LoginForm, InvitationForm, FeedbackForm, \
......@@ -283,7 +283,7 @@ def edit_profile(request, template_name='im/profile.html', extra_context=None):
# providers that user can add
user_available_providers = request.user.get_available_auth_providers()
extra_context['services'] = Service.catalog().values()
extra_context['services'] = Component.catalog().values()
return render_response(template_name,
profile_form=form,
user_providers=user_providers,
......@@ -805,7 +805,7 @@ def remove_auth_provider(request, pk):
@cookie_fix
@signed_terms_required
def landing(request):
context = {'services': Service.catalog(orderfor='dashboard')}
context = {'services': Component.catalog(orderfor='dashboard')}
return render_response(
'im/landing.html',
context_instance=get_context(request), **context)
......@@ -908,7 +908,7 @@ class MenuItem(dict):
def get_services(request):
callback = request.GET.get('callback', None)
mimetype = 'application/json'
data = json.dumps(Service.catalog().values())
data = json.dumps(Component.catalog().values())
if callback:
# Consume session messages. When get_services is loaded from an astakos
......
......@@ -130,8 +130,8 @@
# Whether or not to display projects in astakos menu
# ASTAKOS_PROJECTS_VISIBLE = False
# A way to extend the services presentation metadata
# ASTAKOS_SERVICES_META = {}
# A way to extend the components presentation metadata
# ASTAKOS_COMPONENTS_META = {}
# A way to extend the resources presentation metadata
# ASTAKOS_RESOURCES_META = {}
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