-
Giorgos Korfiatis authored
Assume base project when reconciling astakos resources.
9d5f4be2
functions.py 40.99 KiB
# Copyright 2011, 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 logging
from datetime import datetime
from dateutil.relativedelta import relativedelta
from django.utils.translation import ugettext as _
from django.core.mail import send_mail, get_connection
from django.core.urlresolvers import reverse
from django.contrib.auth import login as auth_login, logout as auth_logout
from django.db.models import Q
from synnefo_branding.utils import render_to_string
from synnefo.util.keypath import set_path
from synnefo.lib import join_urls
from astakos.im.models import AstakosUser, Invitation, ProjectMembership, \
ProjectApplication, Project, new_chain, Resource, ProjectLock, \
create_project, ProjectResourceQuota, ProjectResourceGrant
from astakos.im import quotas
from astakos.im import project_notif
from astakos.im import settings
import astakos.im.messages as astakos_messages
logger = logging.getLogger(__name__)
def login(request, user):
auth_login(request, user)
from astakos.im.models import SessionCatalog
SessionCatalog(
session_key=request.session.session_key,
user=user
).save()
logger.info('%s logged in.', user.log_display)
def logout(request, *args, **kwargs):
user = request.user
auth_logout(request, *args, **kwargs)
user.delete_online_access_tokens()
logger.info('%s logged out.', user.log_display)
def send_verification(user, template_name='im/activation_email.txt'):
"""
Send email to user to verify his/her email and activate his/her account.
"""
url = join_urls(settings.BASE_HOST,
user.get_activation_url(nxt=reverse('index')))
message = render_to_string(template_name, {
'user': user,
'url': url,
'baseurl': settings.BASE_URL,
'site_name': settings.SITENAME,
'support': settings.CONTACT_EMAIL})
sender = settings.SERVER_EMAIL
send_mail(_(astakos_messages.VERIFICATION_EMAIL_SUBJECT), message, sender,
[user.email],
connection=get_connection())
logger.info("Sent user verirfication email: %s", user.log_display)
def _send_admin_notification(template_name,
context=None,
user=None,
msg="",
subject='alpha2 testing notification',):
"""
Send notification email to settings.HELPDESK + settings.MANAGERS +
settings.ADMINS.
"""
if context is None:
context = {}
if not 'user' in context:
context['user'] = user
message = render_to_string(template_name, context)
sender = settings.SERVER_EMAIL
recipient_list = [e[1] for e in settings.HELPDESK +
settings.MANAGERS + settings.ADMINS]
send_mail(subject, message, sender, recipient_list,
connection=get_connection())
if user:
msg = 'Sent admin notification (%s) for user %s' % (msg,
user.log_display)
else:
msg = 'Sent admin notification (%s)' % msg
logger.log(settings.LOGGING_LEVEL, msg)
def send_account_pending_moderation_notification(
user,
template_name='im/account_pending_moderation_notification.txt'):
"""
Notify admins that a new user has verified his email address and moderation
step is required to activate his account.
"""
subject = (_(astakos_messages.ACCOUNT_CREATION_SUBJECT) %
{'user': user.email})
return _send_admin_notification(template_name, {}, subject=subject,
user=user, msg="account creation")
def send_account_activated_notification(
user,
template_name='im/account_activated_notification.txt'):
"""
Send email to settings.HELPDESK + settings.MANAGERES + settings.ADMINS
lists to notify that a new account has been accepted and activated.
"""
message = render_to_string(
template_name,
{'user': user}
)
sender = settings.SERVER_EMAIL
recipient_list = [e[1] for e in settings.HELPDESK +
settings.MANAGERS + settings.ADMINS]
send_mail(_(astakos_messages.HELPDESK_NOTIFICATION_EMAIL_SUBJECT) %
{'user': user.email},
message, sender, recipient_list, connection=get_connection())
msg = 'Sent helpdesk admin notification for %s'
logger.log(settings.LOGGING_LEVEL, msg, user.email)
def send_invitation(invitation, template_name='im/invitation.txt'):
"""
Send invitation email.
"""
subject = _(astakos_messages.INVITATION_EMAIL_SUBJECT)
url = '%s?code=%d' % (join_urls(settings.BASE_HOST,
reverse('index')), invitation.code)
message = render_to_string(template_name, {
'invitation': invitation,
'url': url,
'baseurl': settings.BASE_URL,
'site_name': settings.SITENAME,
'support': settings.CONTACT_EMAIL})
sender = settings.SERVER_EMAIL
send_mail(subject, message, sender, [invitation.username],
connection=get_connection())
msg = 'Sent invitation %s'
logger.log(settings.LOGGING_LEVEL, msg, invitation)
inviter_invitations = invitation.inviter.invitations
invitation.inviter.invitations = max(0, inviter_invitations - 1)
invitation.inviter.save()
def send_greeting(user, email_template_name='im/welcome_email.txt'):
"""
Send welcome email to an accepted/activated user.
Raises SMTPException, socket.error
"""
subject = _(astakos_messages.GREETING_EMAIL_SUBJECT)
message = render_to_string(email_template_name, {
'user': user,
'url': join_urls(settings.BASE_HOST,
reverse('index')),
'baseurl': settings.BASE_URL,
'site_name': settings.SITENAME,
'support': settings.CONTACT_EMAIL})
sender = settings.SERVER_EMAIL
send_mail(subject, message, sender, [user.email],
connection=get_connection())
msg = 'Sent greeting %s'
logger.log(settings.LOGGING_LEVEL, msg, user.log_display)
def send_feedback(msg, data, user, email_template_name='im/feedback_mail.txt'):
subject = _(astakos_messages.FEEDBACK_EMAIL_SUBJECT)
from_email = settings.SERVER_EMAIL
recipient_list = [e[1] for e in settings.HELPDESK]
content = render_to_string(email_template_name, {
'message': msg,
'data': data,
'user': user})
send_mail(subject, content, from_email, recipient_list,
connection=get_connection())
msg = 'Sent feedback from %s'
logger.log(settings.LOGGING_LEVEL, msg, user.log_display)
def send_change_email(ec, request,
email_template_name=
'registration/email_change_email.txt'):
url = ec.get_url()
url = request.build_absolute_uri(url)
c = {'url': url,
'site_name': settings.SITENAME,
'support': settings.CONTACT_EMAIL,
'ec': ec}
message = render_to_string(email_template_name, c)
from_email = settings.SERVER_EMAIL
send_mail(_(astakos_messages.EMAIL_CHANGE_EMAIL_SUBJECT), message,
from_email,
[ec.new_email_address], connection=get_connection())
msg = 'Sent change email for %s'
logger.log(settings.LOGGING_LEVEL, msg, ec.user.log_display)
def invite(inviter, email, realname):
inv = Invitation(inviter=inviter, username=email, realname=realname)
inv.save()
send_invitation(inv)
inviter.invitations = max(0, inviter.invitations - 1)
inviter.save()
### PROJECT FUNCTIONS ###
class ProjectError(Exception):
pass
class ProjectNotFound(ProjectError):
pass
class ProjectForbidden(ProjectError):
pass
class ProjectBadRequest(ProjectError):
pass
class ProjectConflict(ProjectError):
pass
AUTO_ACCEPT_POLICY = 1
MODERATED_POLICY = 2
CLOSED_POLICY = 3
POLICIES = [AUTO_ACCEPT_POLICY, MODERATED_POLICY, CLOSED_POLICY]
def get_related_project_id(application_id):
try:
app = ProjectApplication.objects.get(id=application_id)
return app.chain_id
except ProjectApplication.DoesNotExist:
return None
def get_project_by_id(project_id):
try:
return Project.objects.select_related(
"application", "application__owner",
"application__applicant").get(id=project_id)
except Project.DoesNotExist:
m = _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id
raise ProjectNotFound(m)
def get_project_by_uuid(uuid):
try:
return Project.objects.get(uuid=uuid)
except Project.DoesNotExist:
m = _(astakos_messages.UNKNOWN_PROJECT_ID) % uuid
raise ProjectNotFound(m)
def get_project_for_update(project_id):
try:
try:
project_id = int(project_id)
return Project.objects.select_for_update().get(id=project_id)
except ValueError:
return Project.objects.select_for_update().get(uuid=project_id)
except Project.DoesNotExist:
m = _(astakos_messages.UNKNOWN_PROJECT_ID) % project_id
raise ProjectNotFound(m)
def get_project_of_application_for_update(app_id):
app = get_application(app_id)
return get_project_for_update(app.chain_id)
def get_project_lock():
ProjectLock.objects.select_for_update().get(pk=1)
def get_application(application_id):
try:
return ProjectApplication.objects.get(id=application_id)
except ProjectApplication.DoesNotExist:
m = _(astakos_messages.UNKNOWN_PROJECT_APPLICATION_ID) % application_id
raise ProjectNotFound(m)
def get_project_of_membership_for_update(memb_id):
m = get_membership_by_id(memb_id)
return get_project_for_update(m.project_id)
def get_user_by_uuid(uuid):
try:
return AstakosUser.objects.get(uuid=uuid)
except AstakosUser.DoesNotExist:
m = _(astakos_messages.UNKNOWN_USER_ID) % uuid
raise ProjectNotFound(m)
def get_membership(project_id, user_id):
try:
objs = ProjectMembership.objects.select_related('project', 'person')
return objs.get(project__id=project_id, person__id=user_id)
except ProjectMembership.DoesNotExist:
m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
raise ProjectNotFound(m)
def get_membership_by_id(memb_id):
try:
objs = ProjectMembership.objects.select_related('project', 'person')
return objs.get(id=memb_id)
except ProjectMembership.DoesNotExist:
m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
raise ProjectNotFound(m)
ADMIN_LEVEL = 0
OWNER_LEVEL = 1
APPLICANT_LEVEL = 1
ANY_LEVEL = 2
def is_admin(user):
return not user or user.is_project_admin()
def _failure(silent=False):
if silent:
return False
m = _(astakos_messages.NOT_ALLOWED)
raise ProjectForbidden(m)
def membership_check_allowed(membership, request_user,
level=OWNER_LEVEL, silent=False):
r = project_check_allowed(
membership.project, request_user, level, silent=True)
if r or membership.person == request_user:
return True
return _failure(silent)
def project_check_allowed(project, request_user,
level=OWNER_LEVEL, silent=False):
if is_admin(request_user):
return True
if level <= ADMIN_LEVEL:
return _failure(silent)
if project.owner == request_user:
return True
if level <= OWNER_LEVEL:
return _failure(silent)
if project.state == Project.NORMAL and not project.private \
or bool(project.projectmembership_set.any_accepted().
filter(person=request_user)):
return True
return _failure(silent)
def app_check_allowed(application, request_user,
level=OWNER_LEVEL, silent=False):
if is_admin(request_user):
return True
if level <= ADMIN_LEVEL:
return _failure(silent)
if application.applicant == request_user:
return True
return _failure(silent)
def checkAlive(project):
if not project.is_alive:
m = _(astakos_messages.NOT_ALIVE_PROJECT) % project.uuid
raise ProjectConflict(m)
def accept_membership_project_checks(project, request_user):
project_check_allowed(project, request_user)
checkAlive(project)
join_policy = project.member_join_policy
if join_policy == CLOSED_POLICY:
m = _(astakos_messages.MEMBER_JOIN_POLICY_CLOSED)
raise ProjectConflict(m)
if project.violates_members_limit(adding=1):
m = _(astakos_messages.MEMBER_NUMBER_LIMIT_REACHED)
raise ProjectConflict(m)
def accept_membership_checks(membership, request_user):
if not membership.check_action("accept"):
m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
raise ProjectConflict(m)
project = membership.project
accept_membership_project_checks(project, request_user)
def accept_membership(memb_id, request_user=None, reason=None):
project = get_project_of_membership_for_update(memb_id)
membership = get_membership_by_id(memb_id)
accept_membership_checks(membership, request_user)
user = membership.person
membership.perform_action("accept", actor=request_user, reason=reason)
quotas.qh_sync_membership(membership)
logger.info("User %s has been accepted in %s." %
(user.log_display, project))
project_notif.membership_change_notify(project, user, 'accepted')
return membership
def reject_membership_checks(membership, request_user):
if not membership.check_action("reject"):
m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
raise ProjectConflict(m)
project = membership.project
project_check_allowed(project, request_user)
checkAlive(project)
def reject_membership(memb_id, request_user=None, reason=None):
project = get_project_of_membership_for_update(memb_id)
membership = get_membership_by_id(memb_id)
reject_membership_checks(membership, request_user)
user = membership.person
membership.perform_action("reject", actor=request_user, reason=reason)
logger.info("Request of user %s for %s has been rejected." %
(user.log_display, project))
project_notif.membership_change_notify(project, user, 'rejected')
return membership
def cancel_membership_checks(membership, request_user):
if not membership.check_action("cancel"):
m = _(astakos_messages.NOT_MEMBERSHIP_REQUEST)
raise ProjectConflict(m)
membership_check_allowed(membership, request_user, level=ADMIN_LEVEL)
project = membership.project
checkAlive(project)
def cancel_membership(memb_id, request_user, reason=None):
project = get_project_of_membership_for_update(memb_id)
membership = get_membership_by_id(memb_id)
cancel_membership_checks(membership, request_user)
membership.perform_action("cancel", actor=request_user, reason=reason)
logger.info("Request of user %s for %s has been cancelled." %
(membership.person.log_display, project))
def remove_membership_checks(membership, request_user=None):
if not membership.check_action("remove"):
m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
raise ProjectConflict(m)
project = membership.project
project_check_allowed(project, request_user)
checkAlive(project)
leave_policy = project.member_leave_policy
if leave_policy == CLOSED_POLICY:
m = _(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED)
raise ProjectConflict(m)
def remove_membership(memb_id, request_user=None, reason=None):
project = get_project_of_membership_for_update(memb_id)
membership = get_membership_by_id(memb_id)
remove_membership_checks(membership, request_user)
user = membership.person
membership.perform_action("remove", actor=request_user, reason=reason)
quotas.qh_sync_membership(membership)
logger.info("User %s has been removed from %s." %
(user.log_display, project))
project_notif.membership_change_notify(project, user, 'removed')
return membership
def enroll_member_by_email(project_id, email, request_user=None, reason=None):
try:
user = AstakosUser.objects.accepted().get(email=email)
return enroll_member(project_id, user, request_user, reason=reason)
except AstakosUser.DoesNotExist:
raise ProjectConflict(astakos_messages.UNKNOWN_USERS % email)
def enroll_member(project_id, user, request_user=None, reason=None):
try:
project = get_project_for_update(project_id)
except ProjectNotFound as e:
raise ProjectConflict(e.message)
accept_membership_project_checks(project, request_user)
try:
membership = get_membership(project.id, user.id)
if not membership.check_action("enroll"):
m = _(astakos_messages.MEMBERSHIP_ACCEPTED)
raise ProjectConflict(m)
membership.perform_action("enroll", actor=request_user, reason=reason)
except ProjectNotFound:
membership = new_membership(project, user, actor=request_user,
enroll=True)
quotas.qh_sync_membership(membership)
logger.info("User %s has been enrolled in %s." %
(membership.person.log_display, project))
project_notif.membership_enroll_notify(project, membership.person)
return membership
def leave_project_checks(membership, request_user):
if not membership.check_action("leave"):
m = _(astakos_messages.NOT_ACCEPTED_MEMBERSHIP)
raise ProjectConflict(m)
membership_check_allowed(membership, request_user, level=ADMIN_LEVEL)
project = membership.project
checkAlive(project)
leave_policy = project.member_leave_policy
if leave_policy == CLOSED_POLICY:
m = _(astakos_messages.MEMBER_LEAVE_POLICY_CLOSED)
raise ProjectConflict(m)
def can_cancel_join_request(project, user):
m = user.get_membership(project)
if m is None:
return False
return m.state in [m.REQUESTED]
def can_leave_request(project, user):
m = user.get_membership(project)
if m is None:
return False
try:
leave_project_checks(m, user)
except ProjectError:
return False
return True
def leave_project(memb_id, request_user, reason=None):
project = get_project_of_membership_for_update(memb_id)
membership = get_membership_by_id(memb_id)
leave_project_checks(membership, request_user)
auto_accepted = False
leave_policy = project.member_leave_policy
if leave_policy == AUTO_ACCEPT_POLICY:
membership.perform_action("remove", actor=request_user, reason=reason)
quotas.qh_sync_membership(membership)
logger.info("User %s has left %s." %
(request_user.log_display, project))
auto_accepted = True
else:
membership.perform_action("leave_request", actor=request_user,
reason=reason)
logger.info("User %s requested to leave %s." %
(request_user.log_display, project))
project_notif.membership_request_notify(
project, membership.person, "leave")
return auto_accepted
def join_project_checks(project):
checkAlive(project)
join_policy = project.member_join_policy
if join_policy == CLOSED_POLICY:
m = _(astakos_messages.MEMBER_JOIN_POLICY_CLOSED)
raise ProjectConflict(m)
Nothing = type('Nothing', (), {})
def can_join_request(project, user, membership=Nothing):
try:
join_project_checks(project)
except ProjectError:
return False
m = (membership if membership is not Nothing
else user.get_membership(project))
if not m:
return True
return m.check_action("join")
def new_membership(project, user, actor=None, reason=None, enroll=False):
state = (ProjectMembership.ACCEPTED if enroll
else ProjectMembership.REQUESTED)
m = ProjectMembership.objects.create(
project=project, person=user, state=state, initialized=enroll)
m._log_create(None, state, actor=actor, reason=reason)
return m
def join_project(project_id, request_user, reason=None):
project = get_project_for_update(project_id)
join_project_checks(project)
try:
membership = get_membership(project.id, request_user.id)
if not membership.check_action("join"):
msg = _(astakos_messages.MEMBERSHIP_ASSOCIATED)
raise ProjectConflict(msg)
membership.perform_action("join", actor=request_user, reason=reason)
except ProjectNotFound:
membership = new_membership(project, request_user, actor=request_user,
reason=reason)
join_policy = project.member_join_policy
if (join_policy == AUTO_ACCEPT_POLICY and (
not project.violates_members_limit(adding=1))):
membership.perform_action("accept", actor=request_user, reason=reason)
quotas.qh_sync_membership(membership)
logger.info("User %s joined %s." %
(request_user.log_display, project))
else:
project_notif.membership_request_notify(
project, membership.person, "join")
logger.info("User %s requested to join %s." %
(request_user.log_display, project))
return membership
MEMBERSHIP_ACTION_CHECKS = {
"leave": leave_project_checks,
"cancel": cancel_membership_checks,
"accept": accept_membership_checks,
"reject": reject_membership_checks,
"remove": remove_membership_checks,
}
def membership_allowed_actions(membership, request_user):
allowed = []
for action, check in MEMBERSHIP_ACTION_CHECKS.iteritems():
try:
check(membership, request_user)
allowed.append(action)
except ProjectError:
pass
return allowed
def make_base_project(username):
chain = new_chain()
proj = create_project(
id=chain.chain,
last_application=None,
owner=None,
realname="tmp",
homepage="",
description=("base project for user " + username),
end_date=(datetime.now() + relativedelta(years=100)),
member_join_policy=CLOSED_POLICY,
member_leave_policy=CLOSED_POLICY,
limit_on_members_number=1,
private=True,
is_base=True)
proj.realname = "base:" + proj.uuid
proj.save()
# No quota are set; they will be filled in upon user acceptance
return proj
def enable_base_project(user):
project = user.base_project
_fill_from_skeleton(project)
project.activate()
new_membership(project, user, enroll=True)
quotas.qh_sync_project(project)
MODIFY_KEYS_MAIN = ["owner", "realname", "homepage", "description"]
MODIFY_KEYS_EXTRA = ["end_date", "member_join_policy", "member_leave_policy",
"limit_on_members_number", "private"]
MODIFY_KEYS = MODIFY_KEYS_MAIN + MODIFY_KEYS_EXTRA
def modifies_main_fields(request):
return set(request.keys()).intersection(MODIFY_KEYS_MAIN)
def modify_project(project_id, request):
project = get_project_for_update(project_id)
if project.state not in Project.INITIALIZED_STATES:
m = _(astakos_messages.UNINITIALIZED_NO_MODIFY) % project.uuid
raise ProjectConflict(m)
if project.is_base:
main_fields = modifies_main_fields(request)
if main_fields:
m = (_(astakos_messages.BASE_NO_MODIFY_FIELDS)
% ", ".join(map(str, main_fields)))
raise ProjectBadRequest(m)
new_name = request.get("realname")
if new_name is not None and project.is_alive:
check_conflicting_projects(project, new_name)
project.realname = new_name
project.name = new_name
project.save()
_modify_projects(Project.objects.filter(id=project.id), request)
def modify_projects_in_bulk(flt, request):
main_fields = modifies_main_fields(request)
if main_fields:
raise ProjectBadRequest("Cannot modify field(s) '%s' in bulk" %
", ".join(map(str, main_fields)))
projects = Project.objects.initialized(flt).select_for_update()
_modify_projects(projects, request)
def _modify_projects(projects, request):
upds = {}
for key in MODIFY_KEYS:
value = request.get(key)
if value is not None:
upds[key] = value
projects.update(**upds)
changed_resources = set()
pquotas = []
req_policies = request.get("resources", {})
req_policies = validate_resource_policies(req_policies, admin=True)
for project in projects:
for resource, m_capacity, p_capacity in req_policies:
changed_resources.add(resource)
pquotas.append(
ProjectResourceQuota(
project=project,
resource=resource,
member_capacity=m_capacity,
project_capacity=p_capacity))
ProjectResourceQuota.objects.\
filter(project__in=projects, resource__in=changed_resources).delete()
ProjectResourceQuota.objects.bulk_create(pquotas)
quotas.qh_sync_projects(projects)
def submit_application(owner=None,
name=None,
project_id=None,
homepage=None,
description=None,
start_date=None,
end_date=None,
member_join_policy=None,
member_leave_policy=None,
limit_on_members_number=None,
private=False,
comments=None,
resources=None,
request_user=None):
project = None
if project_id is not None:
project = get_project_for_update(project_id)
project_check_allowed(project, request_user, level=APPLICANT_LEVEL)
if project.state not in Project.INITIALIZED_STATES:
raise ProjectConflict("Cannot modify an uninitialized project.")
policies = validate_resource_policies(resources)
force = request_user.is_project_admin()
ok, limit = qh_add_pending_app(request_user, project, force)
if not ok:
m = _(astakos_messages.REACHED_PENDING_APPLICATION_LIMIT) % limit
raise ProjectConflict(m)
if project is None:
chain = new_chain()
project = create_project(
id=chain.chain,
owner=owner,
realname=name,
homepage=homepage,
description=description,
end_date=end_date,
member_join_policy=member_join_policy,
member_leave_policy=member_leave_policy,
limit_on_members_number=limit_on_members_number,
private=private)
if policies is not None:
set_project_resources(project, policies)
application = ProjectApplication.objects.create(
applicant=request_user,
chain=project,
owner=owner,
name=name,
homepage=homepage,
description=description,
start_date=start_date,
end_date=end_date,
member_join_policy=member_join_policy,
member_leave_policy=member_leave_policy,
limit_on_members_number=limit_on_members_number,
private=private,
comments=comments)
if policies is not None:
set_application_resources(application, policies)
project.last_application = application
project.save()
ProjectApplication.objects.\
filter(chain=project, state=ProjectApplication.PENDING).\
exclude(id=application.id).\
update(state=ProjectApplication.REPLACED)
logger.info("User %s submitted %s." %
(request_user.log_display, application.log_display))
project_notif.application_notify(application, "submit")
return application
def validate_resource_policies(policies, admin=False):
if not isinstance(policies, dict):
raise ProjectBadRequest("Malformed resource policies")
resource_names = policies.keys()
resources = Resource.objects.filter(name__in=resource_names)
if not admin:
resources = resources.filter(api_visible=True)
resource_d = {}
for resource in resources:
resource_d[resource.name] = resource
found = resource_d.keys()
nonex = [name for name in resource_names if name not in found]
if nonex:
raise ProjectBadRequest("Malformed resource policies")
pols = []
for resource_name, specs in policies.iteritems():
p_capacity = specs.get("project_capacity")
m_capacity = specs.get("member_capacity")
if not isinstance(p_capacity, (int, long)) or \
not isinstance(m_capacity, (int, long)):
raise ProjectBadRequest("Malformed resource policies")
pols.append((resource_d[resource_name], m_capacity, p_capacity))
return pols
def set_application_resources(application, policies):
grants = []
for resource, m_capacity, p_capacity in policies:
grants.append(
ProjectResourceGrant(
project_application=application,
resource=resource,
member_capacity=m_capacity,
project_capacity=p_capacity))
ProjectResourceGrant.objects.bulk_create(grants)
def set_project_resources(project, policies):
grants = []
for resource, m_capacity, p_capacity in policies:
grants.append(
ProjectResourceQuota(
project=project,
resource=resource,
member_capacity=m_capacity,
project_capacity=p_capacity))
ProjectResourceQuota.objects.bulk_create(grants)
def check_app_relevant(application, project, project_id):
if project_id is not None and project.uuid != project_id or \
project.last_application != application:
pid = project_id if project_id is not None else project.uuid
m = (_("%s is not a pending application for project %s.") %
(application.id, pid))
raise ProjectConflict(m)
def cancel_application(application_id, project_id=None, request_user=None,
reason=""):
project = get_project_of_application_for_update(application_id)
application = get_application(application_id)
check_app_relevant(application, project, project_id)
app_check_allowed(application, request_user, level=APPLICANT_LEVEL)
if not application.can_cancel():
m = _(astakos_messages.APPLICATION_CANNOT_CANCEL %
(application.id, application.state_display()))
raise ProjectConflict(m)
qh_release_pending_app(application.applicant)
application.cancel(actor=request_user, reason=reason)
if project.state == Project.UNINITIALIZED:
project.set_deleted()
logger.info("%s has been cancelled." % (application.log_display))
def dismiss_application(application_id, project_id=None, request_user=None,
reason=""):
project = get_project_of_application_for_update(application_id)
application = get_application(application_id)
check_app_relevant(application, project, project_id)
app_check_allowed(application, request_user, level=APPLICANT_LEVEL)
if not application.can_dismiss():
m = _(astakos_messages.APPLICATION_CANNOT_DISMISS %
(application.id, application.state_display()))
raise ProjectConflict(m)
application.dismiss(actor=request_user, reason=reason)
if project.state == Project.UNINITIALIZED:
project.set_deleted()
logger.info("%s has been dismissed." % (application.log_display))
def deny_application(application_id, project_id=None, request_user=None,
reason=""):
project = get_project_of_application_for_update(application_id)
application = get_application(application_id)
check_app_relevant(application, project, project_id)
app_check_allowed(application, request_user, level=ADMIN_LEVEL)
if not application.can_deny():
m = _(astakos_messages.APPLICATION_CANNOT_DENY %
(application.id, application.state_display()))
raise ProjectConflict(m)
qh_release_pending_app(application.applicant)
application.deny(actor=request_user, reason=reason)
logger.info("%s has been denied with reason \"%s\"." %
(application.log_display, reason))
project_notif.application_notify(application, "deny")
def check_conflicting_projects(project, new_project_name):
try:
q = Q(name=new_project_name) & ~Q(id=project.id)
conflicting_project = Project.objects.get(q)
m = (_("cannot approve: project with name '%s' "
"already exists (id: %s)") %
(new_project_name, conflicting_project.uuid))
raise ProjectConflict(m) # invalid argument
except Project.DoesNotExist:
pass
def approve_application(application_id, project_id=None, request_user=None,
reason=""):
get_project_lock()
project = get_project_of_application_for_update(application_id)
application = get_application(application_id)
check_app_relevant(application, project, project_id)
app_check_allowed(application, request_user, level=ADMIN_LEVEL)
if not application.can_approve():
m = _(astakos_messages.APPLICATION_CANNOT_APPROVE %
(application.id, application.state_display()))
raise ProjectConflict(m)
if application.name:
check_conflicting_projects(project, application.name)
qh_release_pending_app(application.applicant)
application.approve(actor=request_user, reason=reason)
if project.state == Project.UNINITIALIZED:
_fill_from_skeleton(project)
else:
_apply_modifications(project, application)
project.activate(actor=request_user, reason=reason)
quotas.qh_sync_project(project)
logger.info("%s has been approved." % (application.log_display))
project_notif.application_notify(application, "approve")
return project
def _fill_from_skeleton(project):
current_resources = set(ProjectResourceQuota.objects.
filter(project=project).
values_list("resource_id", flat=True))
resources = Resource.objects.all()
new_quotas = []
for resource in resources:
if resource.id not in current_resources:
limit = quotas.pick_limit_scheme(project, resource)
new_quotas.append(
ProjectResourceQuota(
project=project,
resource=resource,
member_capacity=limit,
project_capacity=limit))
ProjectResourceQuota.objects.bulk_create(new_quotas)
def _apply_modifications(project, application):
FIELDS = [
("owner", "owner"),
("name", "realname"),
("homepage", "homepage"),
("description", "description"),
("end_date", "end_date"),
("member_join_policy", "member_join_policy"),
("member_leave_policy", "member_leave_policy"),
("limit_on_members_number", "limit_on_members_number"),
("private", "private"),
]
changed = False
for appfield, projectfield in FIELDS:
value = getattr(application, appfield)
if value is not None:
changed = True
setattr(project, projectfield, value)
if changed:
project.save()
grants = application.projectresourcegrant_set.all()
pquotas = []
resources = []
for grant in grants:
resources.append(grant.resource)
pquotas.append(
ProjectResourceQuota(
project=project,
resource=grant.resource,
member_capacity=grant.member_capacity,
project_capacity=grant.project_capacity))
ProjectResourceQuota.objects.\
filter(project=project, resource__in=resources).delete()
ProjectResourceQuota.objects.bulk_create(pquotas)
def check_expiration(execute=False):
objects = Project.objects
expired = objects.expired_projects()
if execute:
for project in expired:
terminate(project.pk)
return [project.expiration_info() for project in expired]
def terminate(project_id, request_user=None, reason=None):
project = get_project_for_update(project_id)
project_check_allowed(project, request_user, level=ADMIN_LEVEL)
checkAlive(project)
project.terminate(actor=request_user, reason=reason)
quotas.qh_sync_project(project)
logger.info("%s has been terminated." % (project))
project_notif.project_notify(project, "terminate")
def suspend(project_id, request_user=None, reason=None):
project = get_project_for_update(project_id)
project_check_allowed(project, request_user, level=ADMIN_LEVEL)
checkAlive(project)
project.suspend(actor=request_user, reason=reason)
quotas.qh_sync_project(project)
logger.info("%s has been suspended." % (project))
project_notif.project_notify(project, "suspend")
def unsuspend(project_id, request_user=None, reason=None):
project = get_project_for_update(project_id)
project_check_allowed(project, request_user, level=ADMIN_LEVEL)
if not project.is_suspended:
m = _(astakos_messages.NOT_SUSPENDED_PROJECT) % project.uuid
raise ProjectConflict(m)
project.resume(actor=request_user, reason=reason)
quotas.qh_sync_project(project)
logger.info("%s has been unsuspended." % (project))
project_notif.project_notify(project, "unsuspend")
def reinstate(project_id, request_user=None, reason=None):
get_project_lock()
project = get_project_for_update(project_id)
project_check_allowed(project, request_user, level=ADMIN_LEVEL)
if not project.is_terminated:
m = _(astakos_messages.NOT_TERMINATED_PROJECT) % project.uuid
raise ProjectConflict(m)
check_conflicting_projects(project, project.realname)
project.resume(actor=request_user, reason=reason)
quotas.qh_sync_project(project)
logger.info("%s has been reinstated" % (project))
project_notif.project_notify(project, "reinstate")
def _partition_by(f, l):
d = {}
for x in l:
group = f(x)
group_l = d.get(group, [])
group_l.append(x)
d[group] = group_l
return d
def count_pending_app(users):
users = list(users)
apps = ProjectApplication.objects.filter(state=ProjectApplication.PENDING,
owner__in=users)
apps_d = _partition_by(lambda a: a.owner.uuid, apps)
usage = {}
for user in users:
uuid = user.uuid
set_path(usage,
[uuid, user.base_project.uuid, quotas.PENDING_APP_RESOURCE],
len(apps_d.get(uuid, [])), createpath=True)
return usage
def get_pending_app_diff(project):
if project is None:
diff = 1
else:
objs = ProjectApplication.objects
q = objs.filter(chain=project, state=ProjectApplication.PENDING)
count = q.count()
diff = 1 - count
return diff
def qh_add_pending_app(user, project=None, force=False, assign_project=None):
if assign_project is None:
assign_project = user.base_project
diff = get_pending_app_diff(project)
return quotas.register_pending_apps(user, assign_project,
diff, force=force)
def check_pending_app_quota(user, project=None):
diff = get_pending_app_diff(project)
quota = quotas.get_pending_app_quota(user)
limit = quota['limit']
usage = quota['usage']
if usage + diff > limit:
return False, limit
return True, None
def qh_release_pending_app(user, assign_project=None):
if assign_project is None:
assign_project = user.base_project
quotas.register_pending_apps(user, assign_project, -1)