Commit 7ee2acd3 authored by Giorgos Korfiatis's avatar Giorgos Korfiatis

astakos: Add command project-modify

Add a management command that directly modifies an initialized project.
parent d8075556
......@@ -721,6 +721,76 @@ def enable_base_project(user):
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,
......@@ -798,13 +868,15 @@ def submit_application(owner=None,
return application
def validate_resource_policies(policies):
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,
api_visible=True)
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
......
# 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 optparse import make_option
from django.db.models import Q
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from synnefo.util import units
from astakos.im import functions
from astakos.im import models
import astakos.api.projects as api
import synnefo.util.date as date_util
from snf_django.management import utils
from astakos.im.management.commands import _common
def make_policies(limits):
policies = {}
for (name, member_capacity, project_capacity) in limits:
try:
member_capacity = units.parse(member_capacity)
project_capacity = units.parse(project_capacity)
except units.ParseError:
m = "Please specify capacity as a decimal integer"
raise CommandError(m)
policies[name] = {"member_capacity": member_capacity,
"project_capacity": project_capacity}
return policies
Simple = type('Simple', (), {})
class Param(object):
def __init__(self, key=Simple, mod=Simple, action=Simple, nargs=Simple,
is_main=False, help=""):
self.key = key
self.mod = mod
self.action = action
self.nargs = nargs
self.is_main = is_main
self.help = help
PARAMS = {
"name": Param(key="realname", help="Set project name"),
"owner": Param(mod=_common.get_accepted_user, help="Set project owner"),
"homepage": Param(help="Set project homepage"),
"description": Param(help="Set project description"),
"end_date": Param(mod=date_util.isoparse, is_main=True,
help=("Set project end date in ISO format "
"(e.g. 2014-01-01T00:00Z)")),
"join_policy": Param(key="member_join_policy", is_main=True,
mod=(lambda x: api.MEMBERSHIP_POLICY[x]),
help="Set join policy (auto, moderated, or closed)"),
"leave_policy": Param(key="member_leave_policy", is_main=True,
mod=(lambda x: api.MEMBERSHIP_POLICY[x]),
help=("Set leave policy "
"(auto, moderated, or closed)")),
"max_members": Param(key="limit_on_members_number", mod=int, is_main=True,
help="Set maximum members limit"),
"private": Param(mod=utils.parse_bool, is_main=True,
help="Set project private"),
"limit": Param(key="resources", mod=make_policies, is_main=True,
nargs=3, action="append",
help=("Set resource limits: "
"resource_name member_capacity project_capacity")),
}
def make_options():
options = []
for key, param in PARAMS.iteritems():
opt = "--" + key.replace('_', '-')
kwargs = {}
if param.action is not Simple:
kwargs["action"] = param.action
if param.nargs is not Simple:
kwargs["nargs"] = param.nargs
kwargs["help"] = param.help
options.append(make_option(opt, **kwargs))
return tuple(options)
class Command(BaseCommand):
args = "<project id> (or --all-base-projects)"
help = "Modify an already initialized project"
option_list = BaseCommand.option_list + make_options() + (
make_option('--all-base-projects',
action='store_true',
default=False,
help="Modify in bulk all initialized base projects"),
make_option('--exclude',
help=("If `--all-base-projects' is given, exclude projects"
" given as a list of uuids: uuid1,uuid2,uuid3")),
)
def check_args(self, args, all_base, exclude):
if all_base and args or not all_base and len(args) != 1:
m = "Please provide a project ID or --all-base-projects"
raise CommandError(m)
if not all_base and exclude:
m = ("Option --exclude is meaningful only combined with "
" --all-base-projects.")
raise CommandError(m)
def mk_all_base_filter(self, all_base, exclude):
flt = Q(state__in=models.Project.INITIALIZED_STATES, is_base=True)
if exclude:
exclude = exclude.split(',')
flt &= ~Q(uuid__in=exclude)
return flt
@transaction.commit_on_success
def handle(self, *args, **options):
all_base = options["all_base_projects"]
exclude = options["exclude"]
self.check_args(args, all_base, exclude)
try:
changes = {}
for key, value in options.iteritems():
param = PARAMS.get(key)
if param is None or value is None:
continue
if all_base and not param.is_main:
m = "Cannot modify field '%s' in bulk" % key
raise CommandError(m)
k = key if param.key is Simple else param.key
v = value if param.mod is Simple else param.mod(value)
changes[k] = v
if all_base:
flt = self.mk_all_base_filter(all_base, exclude)
functions.modify_projects_in_bulk(flt, changes)
else:
functions.modify_project(args[0], changes)
except BaseException as e:
raise CommandError(e)
......@@ -273,6 +273,8 @@ APPLICATION_CANNOT_CANCEL = "Cannot cancel application %s in state '%s'"
APPLICATION_CANCELLED = "Your project application has been cancelled."
REACHED_PENDING_APPLICATION_LIMIT = ("You have reached the maximum number "
"of pending project applications: %s.")
UNINITIALIZED_NO_MODIFY = "Cannot modify: project %s is not initialized."
BASE_NO_MODIFY_FIELDS = "Cannot modify field(s) '%s' of base projects."
PENDING_APPLICATION_LIMIT_ADD = \
("You are not allowed to create a new project "
......
......@@ -658,18 +658,18 @@ class Astakos(SynnefoComponent):
]
def modify_all_quota(self):
cmd = "snf-manage user-modify -f --all --base-quota"
return [
"%s pithos.diskspace 40G" % cmd,
"%s astakos.pending_app 2" % cmd,
"%s cyclades.vm 4" % cmd,
"%s cyclades.disk 40G" % cmd,
"%s cyclades.total_ram 16G" % cmd,
"%s cyclades.ram 8G" % cmd,
"%s cyclades.total_cpu 32" % cmd,
"%s cyclades.cpu 16" % cmd,
"%s cyclades.network.private 4" % cmd,
"%s cyclades.floating_ip 4" % cmd,
cmd = "snf-manage project-modify --all-base-projects --limit"
return [
"%s pithos.diskspace 40G 40G" % cmd,
"%s astakos.pending_app 2 2" % cmd,
"%s cyclades.vm 4 4" % cmd,
"%s cyclades.disk 40G 40G" % cmd,
"%s cyclades.total_ram 16G 16G" % cmd,
"%s cyclades.ram 8G 8G" % cmd,
"%s cyclades.total_cpu 32 32" % cmd,
"%s cyclades.cpu 16 16" % cmd,
"%s cyclades.network.private 4 4" % cmd,
"%s cyclades.floating_ip 4 4" % cmd,
]
def get_services(self):
......
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