Added UpdateProjectQuota
Accessable at v1/openstack/quotas/. A GET request returns json specifiing quota size, the current quota, the current usage, and some details of any currently active quota change tasks. A POST request will update the quota for the project to a given size. The data must contain a JSON dict with 'size' in it, which will be the name of one of the pre-defined sizes. Optionally regions (a list of region names) can be specified which will restrict the update operation to those regions. Change-Id: I907664f79f6eef0b5239139999cc7a28d246e446
This commit is contained in:
parent
5e16b575ad
commit
6f60b059f4
@ -133,6 +133,14 @@ For ease of integration with OpenStack, these endpoints are setup to work and pa
|
||||
* setup basic networking if needed
|
||||
* create user with random password
|
||||
* set user given password on token submit
|
||||
* ../v1/openstack/quotas/ - GET
|
||||
* JSON containg the specifications of each quota size, and data about the quota size for all regions in the current project
|
||||
* An additional parameter regions, containing a comma separated list of regions can be passed as well to limit the regions it will return data about.
|
||||
* ../v1/openstack/quotas/ - POST
|
||||
* Change the quota for all regions
|
||||
* The quota will automatically update if the new quota level is adjacent to the current one and there has not been an update to that region in the past 30 days
|
||||
* Other options will require admin approval before updating
|
||||
* POST body should be a JSON dict containing the size ('size') and optionally 'regions', a list of regions to update to
|
||||
|
||||
|
||||
#### (DEPRECATED) Default TaskView Endpoints:
|
||||
|
@ -217,3 +217,6 @@ class IdentityManager(object):
|
||||
except ks_exceptions.NotFound:
|
||||
region = None
|
||||
return region
|
||||
|
||||
def list_regions(self, **kwargs):
|
||||
return self.ks_client.regions.list(**kwargs)
|
||||
|
@ -17,6 +17,7 @@ from logging import getLogger
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
from adjutant.common.quota import QuotaManager
|
||||
from adjutant.actions import user_store
|
||||
from adjutant.actions.models import Action
|
||||
|
||||
@ -219,6 +220,17 @@ class ResourceMixin(object):
|
||||
self.domain_id = self.domain.id
|
||||
return True
|
||||
|
||||
def _validate_region_exists(self, region):
|
||||
# Check that the region actually exists
|
||||
id_manager = user_store.IdentityManager()
|
||||
v_region = id_manager.get_region(region)
|
||||
if not v_region:
|
||||
self.add_note('ERROR: Region: %s does not exist.' % region)
|
||||
return False
|
||||
self.add_note('Region: %s exists.' % region)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class UserMixin(ResourceMixin):
|
||||
"""Mixin with functions for users."""
|
||||
@ -401,6 +413,44 @@ class ProjectMixin(ResourceMixin):
|
||||
self.add_note("New project '%s' created." % project.name)
|
||||
|
||||
|
||||
class QuotaMixin(ResourceMixin):
|
||||
"""Mixin with functions for dealing with quotas and limits."""
|
||||
|
||||
def _region_usage_greater_than_quota(self, usage, quota):
|
||||
for service, values in quota.items():
|
||||
for resource, value in values.items():
|
||||
try:
|
||||
if usage[service][resource] > value and value >= 0:
|
||||
return True
|
||||
except KeyError:
|
||||
pass
|
||||
return False
|
||||
|
||||
def _usage_greater_than_quota(self, regions):
|
||||
quota_manager = QuotaManager(
|
||||
self.project_id,
|
||||
size_difference_threshold=self.size_difference_threshold)
|
||||
quota = settings.PROJECT_QUOTA_SIZES.get(self.size, {})
|
||||
for region in regions:
|
||||
current_usage = quota_manager.get_current_usage(region)
|
||||
if self._region_usage_greater_than_quota(current_usage, quota):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _validate_regions_exist(self):
|
||||
# Check that all the regions in the list exist
|
||||
for region in self.regions:
|
||||
if not self._validate_region_exists(region):
|
||||
return False
|
||||
return True
|
||||
|
||||
def _validate_usage_lower_than_quota(self):
|
||||
if self._usage_greater_than_quota(self.regions):
|
||||
self.add_note("Current usage greater than new quota")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class UserIdAction(BaseAction):
|
||||
|
||||
def _get_target_user(self):
|
||||
|
@ -22,7 +22,7 @@ from adjutant.actions.v1.users import (
|
||||
UpdateUserEmailAction)
|
||||
from adjutant.actions.v1.resources import (
|
||||
NewDefaultNetworkAction, NewProjectDefaultNetworkAction,
|
||||
SetProjectQuotaAction)
|
||||
SetProjectQuotaAction, UpdateProjectQuotasAction)
|
||||
from adjutant.actions.v1.misc import SendAdditionalEmailAction
|
||||
|
||||
|
||||
@ -56,6 +56,8 @@ register_action_class(
|
||||
serializers.NewProjectDefaultNetworkSerializer)
|
||||
register_action_class(
|
||||
SetProjectQuotaAction, serializers.SetProjectQuotaSerializer)
|
||||
register_action_class(
|
||||
UpdateProjectQuotasAction, serializers.UpdateProjectQuotasSerializer)
|
||||
|
||||
# Register Misc actions:
|
||||
register_action_class(
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2015 Catalyst IT Ltd
|
||||
# Copyright (C) 2015 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
@ -12,9 +12,15 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from adjutant.actions.v1.base import BaseAction, ProjectMixin
|
||||
from django.conf import settings
|
||||
from adjutant.actions.v1.base import BaseAction, ProjectMixin, QuotaMixin
|
||||
from adjutant.actions import openstack_clients, user_store
|
||||
from adjutant.api import models
|
||||
from adjutant.common.quota import QuotaManager
|
||||
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class NewDefaultNetworkAction(BaseAction, ProjectMixin):
|
||||
@ -218,8 +224,16 @@ class NewProjectDefaultNetworkAction(NewDefaultNetworkAction):
|
||||
self._create_network()
|
||||
|
||||
|
||||
class SetProjectQuotaAction(BaseAction):
|
||||
""" Updates quota for a given project to a configured quota level """
|
||||
class UpdateProjectQuotasAction(BaseAction, QuotaMixin):
|
||||
""" Updates quota for a project to a given size in a list of regions """
|
||||
|
||||
required = [
|
||||
'size',
|
||||
'project_id',
|
||||
'regions',
|
||||
]
|
||||
|
||||
default_days_between_autoapprove = 30
|
||||
|
||||
class ServiceQuotaFunctor(object):
|
||||
def __call__(self, project_id, values):
|
||||
@ -252,35 +266,161 @@ class SetProjectQuotaAction(BaseAction):
|
||||
'neutron': ServiceQuotaNeutronFunctor
|
||||
}
|
||||
|
||||
def _validate_project_exists(self):
|
||||
if not self.project_id:
|
||||
self.add_note('No project_id set, previous action should have '
|
||||
'set it.')
|
||||
return False
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UpdateProjectQuotasAction, self).__init__(*args, **kwargs)
|
||||
self.size_difference_threshold = settings.TASK_SETTINGS.get(
|
||||
self.action.task.task_type, {}).get(
|
||||
'size_difference_threshold')
|
||||
|
||||
id_manager = user_store.IdentityManager()
|
||||
project = id_manager.get_project(self.project_id)
|
||||
if not project:
|
||||
self.add_note('Project with id %s does not exist.' %
|
||||
self.project_id)
|
||||
def _get_email(self):
|
||||
|
||||
if settings.USERNAME_IS_EMAIL:
|
||||
return self.action.task.keystone_user['username']
|
||||
else:
|
||||
id_manager = user_store.IdentityManager()
|
||||
user = id_manager.users.get(self.keystone_user['user_id'])
|
||||
email = user.email
|
||||
if email:
|
||||
return email
|
||||
|
||||
self.add_note("User email address not set.")
|
||||
return None
|
||||
|
||||
def _validate_quota_size_exists(self):
|
||||
size_list = settings.PROJECT_QUOTA_SIZES.keys()
|
||||
if self.size not in size_list:
|
||||
self.add_note("Quota size: %s does not exist" % self.size)
|
||||
return False
|
||||
self.add_note('Project with id %s exists.' % self.project_id)
|
||||
return True
|
||||
|
||||
def _pre_validate(self):
|
||||
# Nothing to validate yet.
|
||||
self.action.valid = True
|
||||
self.action.save()
|
||||
def _set_region_quota(self, region_name, quota_size):
|
||||
# Set the quota for an individual region
|
||||
quota_settings = settings.PROJECT_QUOTA_SIZES.get(quota_size, {})
|
||||
if not quota_settings:
|
||||
self.add_note(
|
||||
"Project quota not defined for size '%s' in region %s." % (
|
||||
quota_size, region_name))
|
||||
return
|
||||
|
||||
for service_name, values in quota_settings.items():
|
||||
updater_class = self._quota_updaters.get(service_name)
|
||||
if not updater_class:
|
||||
self.add_note("No quota updater found for %s. Ignoring" %
|
||||
service_name)
|
||||
continue
|
||||
# functor for the service+region
|
||||
service_functor = updater_class(region_name)
|
||||
service_functor(self.project_id, values)
|
||||
self.add_note("Project quota for region %s set to %s" % (
|
||||
region_name, quota_size))
|
||||
|
||||
def _can_auto_approve(self):
|
||||
wait_days = self.settings.get('days_between_autoapprove',
|
||||
self.default_days_between_autoapprove)
|
||||
task_list = models.Task.objects.filter(
|
||||
completed_on__gte=timezone.now() - timedelta(days=wait_days),
|
||||
task_type__exact=self.action.task.task_type,
|
||||
cancelled__exact=False,
|
||||
project_id__exact=self.project_id)
|
||||
|
||||
# Check to see if there have been any updates in the relavent regions
|
||||
# recently
|
||||
for task in task_list:
|
||||
for action in task.actions:
|
||||
intersect = set(action.action_data[
|
||||
'regions']).intersection(self.regions)
|
||||
if intersect:
|
||||
self.add_note(
|
||||
"Quota has already been updated within the auto "
|
||||
"approve time limit.")
|
||||
return False
|
||||
|
||||
region_sizes = []
|
||||
|
||||
quota_manager = QuotaManager(self.project_id,
|
||||
self.size_difference_threshold)
|
||||
|
||||
for region in self.regions:
|
||||
current_size = quota_manager.get_region_quota_data(
|
||||
region)['current_quota_size']
|
||||
region_sizes.append(current_size)
|
||||
self.add_note(
|
||||
"Project has size '%s' in region: '%s'" %
|
||||
(current_size, region))
|
||||
|
||||
# Check for preapproved_quotas
|
||||
preapproved_quotas = []
|
||||
|
||||
# If all region sizes are the same
|
||||
if region_sizes.count(region_sizes[0]) == len(region_sizes):
|
||||
preapproved_quotas = quota_manager.get_quota_change_options(
|
||||
region_sizes[0])
|
||||
|
||||
if self.size not in preapproved_quotas:
|
||||
self.add_note(
|
||||
"Quota size '%s' not in preapproved list: %s" %
|
||||
(self.size, preapproved_quotas))
|
||||
return False
|
||||
self.add_note(
|
||||
"Quota size '%s' in preapproved list: %s" %
|
||||
(self.size, preapproved_quotas))
|
||||
return True
|
||||
|
||||
def _validate(self):
|
||||
# Make sure the project id is valid and can be used
|
||||
self.action.valid = (
|
||||
self._validate_project_exists()
|
||||
self._validate_project_id() and
|
||||
self._validate_quota_size_exists() and
|
||||
self._validate_regions_exist() and
|
||||
self._validate_usage_lower_than_quota()
|
||||
)
|
||||
self.action.save()
|
||||
|
||||
def _pre_approve(self):
|
||||
self._pre_validate()
|
||||
self._validate()
|
||||
# Set auto-approval
|
||||
self.set_auto_approve(self._can_auto_approve())
|
||||
|
||||
def _post_approve(self):
|
||||
self._validate()
|
||||
|
||||
if not self.valid or self.action.state == "completed":
|
||||
return
|
||||
|
||||
for region in self.regions:
|
||||
self._set_region_quota(region, self.size)
|
||||
|
||||
self.action.state = "completed"
|
||||
self.action.task.cache['project_id'] = self.project_id
|
||||
self.action.task.cache['size'] = self.size
|
||||
|
||||
self.action.save()
|
||||
|
||||
def _submit(self, token_data):
|
||||
"""
|
||||
Nothing to do here. Everything is done at post_approve.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class SetProjectQuotaAction(UpdateProjectQuotasAction):
|
||||
""" Updates quota for a given project to a configured quota level """
|
||||
required = []
|
||||
|
||||
def _get_email(self):
|
||||
return None
|
||||
|
||||
def _validate(self):
|
||||
# Make sure the project id is valid and can be used
|
||||
self.action.valid = (
|
||||
self._validate_project_id()
|
||||
)
|
||||
self.action.save()
|
||||
|
||||
def _pre_approve(self):
|
||||
# Nothing to validate yet
|
||||
self.action.valid = True
|
||||
self.action.save()
|
||||
|
||||
def _post_approve(self):
|
||||
# Assumption: another action has placed the project_id into the cache.
|
||||
@ -294,24 +434,7 @@ class SetProjectQuotaAction(BaseAction):
|
||||
regions_dict = self.settings.get('regions', {})
|
||||
for region_name, region_settings in regions_dict.items():
|
||||
quota_size = region_settings.get('quota_size')
|
||||
quota_settings = settings.PROJECT_QUOTA_SIZES.get(quota_size, {})
|
||||
if not quota_settings:
|
||||
self.add_note(
|
||||
"Project quota not defined for size '%s' in region %s." % (
|
||||
quota_size, region_name))
|
||||
continue
|
||||
for service_name, values in quota_settings.items():
|
||||
updater_class = self._quota_updaters.get(service_name)
|
||||
if not updater_class:
|
||||
self.add_note("No quota updater found for %s. Ignoring" %
|
||||
service_name)
|
||||
continue
|
||||
# functor for the service+region
|
||||
service_functor = updater_class(region_name)
|
||||
service_functor(self.project_id, values)
|
||||
self.add_note(
|
||||
"Project quota for region %s set to %s" % (
|
||||
region_name, quota_size))
|
||||
self._set_region_quota(region_name, quota_size)
|
||||
|
||||
self.action.state = "completed"
|
||||
self.action.save()
|
||||
|
@ -14,12 +14,18 @@
|
||||
|
||||
from rest_framework import serializers
|
||||
from django.conf import settings
|
||||
from adjutant.actions import user_store
|
||||
|
||||
|
||||
role_options = settings.DEFAULT_ACTION_SETTINGS.get("NewUserAction", {}).get(
|
||||
"allowed_roles", [])
|
||||
|
||||
|
||||
def get_region_choices():
|
||||
id_manager = user_store.IdentityManager()
|
||||
return (region.id for region in id_manager.list_regions())
|
||||
|
||||
|
||||
class BaseUserNameSerializer(serializers.Serializer):
|
||||
"""
|
||||
A serializer where the user is identified by username/email.
|
||||
@ -95,3 +101,26 @@ class SendAdditionalEmailSerializer(serializers.Serializer):
|
||||
|
||||
class UpdateUserEmailSerializer(BaseUserIdSerializer):
|
||||
new_email = serializers.EmailField()
|
||||
|
||||
|
||||
class UpdateProjectQuotasSerializer(serializers.Serializer):
|
||||
project_id = serializers.CharField(max_length=64)
|
||||
size = serializers.CharField(max_length=64)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UpdateProjectQuotasSerializer, self).__init__(*args, **kwargs)
|
||||
# NOTE(amelia): This overide is mostly in use so that it can be tested
|
||||
# However it does take into account the improbable edge case that the
|
||||
# regions have changed since the server was last started
|
||||
self.fields['regions'] = serializers.MultipleChoiceField(
|
||||
choices=get_region_choices())
|
||||
|
||||
def validate_size(self, value):
|
||||
"""
|
||||
Check that the size exists in the conf.
|
||||
"""
|
||||
size_list = settings.PROJECT_QUOTA_SIZES.keys()
|
||||
if value not in size_list:
|
||||
raise serializers.ValidationError("Quota size: %s is not valid"
|
||||
% value)
|
||||
return value
|
||||
|
@ -151,8 +151,8 @@ class FakeNovaClient(FakeOpenstackClient):
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def get(self, project_id):
|
||||
return self.LimitFake(self.data, project_id)
|
||||
def get(self, tenant_id):
|
||||
return self.LimitFake(self.data, tenant_id)
|
||||
|
||||
class LimitFake(object):
|
||||
def __init__(self, data, project_id):
|
||||
|
@ -13,12 +13,13 @@
|
||||
# under the License.
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
|
||||
import mock
|
||||
|
||||
from adjutant.actions.v1.resources import (
|
||||
NewDefaultNetworkAction, NewProjectDefaultNetworkAction,
|
||||
SetProjectQuotaAction)
|
||||
SetProjectQuotaAction, UpdateProjectQuotasAction)
|
||||
from adjutant.api.models import Task
|
||||
from adjutant.api.v1.tests import (FakeManager, setup_temp_cache,
|
||||
modify_dict_settings)
|
||||
@ -454,9 +455,204 @@ class ProjectSetupActionTests(TestCase):
|
||||
neutronquota = neutron_cache['RegionOne']['test_project_id']['quota']
|
||||
self.assertEquals(neutronquota['network'], 3)
|
||||
|
||||
# RegionTwo, cinder only
|
||||
self.assertFalse('RegionTwo' in nova_cache)
|
||||
r2_cinderquota = cinder_cache['RegionTwo']['test_project_id']['quota']
|
||||
self.assertEquals(r2_cinderquota['gigabytes'], 73571)
|
||||
self.assertEquals(r2_cinderquota['snapshots'], 73572)
|
||||
self.assertEquals(r2_cinderquota['volumes'], 73573)
|
||||
# RegionThree, cinder only
|
||||
self.assertFalse('RegionThree' in nova_cache)
|
||||
r2_cinderquota = cinder_cache['RegionThree'][
|
||||
'test_project_id']['quota']
|
||||
self.assertEquals(r2_cinderquota['gigabytes'], 50000)
|
||||
self.assertEquals(r2_cinderquota['snapshots'], 600)
|
||||
self.assertEquals(r2_cinderquota['volumes'], 200)
|
||||
|
||||
|
||||
@mock.patch(
|
||||
'adjutant.actions.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'adjutant.common.quota.get_neutronclient',
|
||||
get_fake_neutron)
|
||||
@mock.patch(
|
||||
'adjutant.common.quota.get_novaclient',
|
||||
get_fake_novaclient)
|
||||
@mock.patch(
|
||||
'adjutant.common.quota.get_cinderclient',
|
||||
get_fake_cinderclient)
|
||||
@mock.patch(
|
||||
'adjutant.actions.v1.resources.' +
|
||||
'openstack_clients.get_neutronclient',
|
||||
get_fake_neutron)
|
||||
@mock.patch(
|
||||
'adjutant.actions.v1.resources.' +
|
||||
'openstack_clients.get_novaclient',
|
||||
get_fake_novaclient)
|
||||
@mock.patch(
|
||||
'adjutant.actions.v1.resources.' +
|
||||
'openstack_clients.get_cinderclient',
|
||||
get_fake_cinderclient)
|
||||
class QuotaActionTests(TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
cinder_cache.clear()
|
||||
nova_cache.clear()
|
||||
neutron_cache.clear()
|
||||
|
||||
def test_update_quota(self):
|
||||
"""
|
||||
Sets a new quota on all services of a project in a single region
|
||||
"""
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project, }, {user.id: user})
|
||||
setup_neutron_cache('RegionOne', 'test_project_id')
|
||||
|
||||
# Test sending to only a single region
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
|
||||
|
||||
data = {
|
||||
'project_id': 'test_project_id',
|
||||
'size': 'medium',
|
||||
'regions': ['RegionOne'],
|
||||
'user_id': user.id
|
||||
}
|
||||
|
||||
action = UpdateProjectQuotasAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
# check the quotas were updated
|
||||
# This relies on test_settings heavily.
|
||||
cinderquota = cinder_cache['RegionOne']['test_project_id']['quota']
|
||||
self.assertEquals(cinderquota['gigabytes'], 10000)
|
||||
novaquota = nova_cache['RegionOne']['test_project_id']['quota']
|
||||
self.assertEquals(novaquota['ram'], 327680)
|
||||
neutronquota = neutron_cache['RegionOne']['test_project_id']['quota']
|
||||
self.assertEquals(neutronquota['network'], 5)
|
||||
|
||||
def test_update_quota_multi_region(self):
|
||||
"""
|
||||
Sets a new quota on all services of a project in multiple regions
|
||||
"""
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project, }, {user.id: user})
|
||||
setup_mock_caches('RegionOne', project.id)
|
||||
setup_mock_caches('RegionTwo', project.id)
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
|
||||
|
||||
data = {
|
||||
'project_id': 'test_project_id',
|
||||
'size': 'large',
|
||||
'domain_id': 'default',
|
||||
'regions': ['RegionOne', 'RegionTwo'],
|
||||
'user_id': 'user_id'
|
||||
}
|
||||
|
||||
action = UpdateProjectQuotasAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
# check the quotas were updated
|
||||
# This relies on test_settings heavily.
|
||||
cinderquota = cinder_cache['RegionOne']['test_project_id']['quota']
|
||||
self.assertEquals(cinderquota['gigabytes'], 50000)
|
||||
novaquota = nova_cache['RegionOne']['test_project_id']['quota']
|
||||
self.assertEquals(novaquota['ram'], 655360)
|
||||
neutronquota = neutron_cache['RegionOne']['test_project_id']['quota']
|
||||
self.assertEquals(neutronquota['network'], 10)
|
||||
|
||||
cinderquota = cinder_cache['RegionTwo']['test_project_id']['quota']
|
||||
self.assertEquals(cinderquota['gigabytes'], 50000)
|
||||
novaquota = nova_cache['RegionTwo']['test_project_id']['quota']
|
||||
self.assertEquals(novaquota['ram'], 655360)
|
||||
neutronquota = neutron_cache['RegionTwo']['test_project_id']['quota']
|
||||
self.assertEquals(neutronquota['network'], 10)
|
||||
|
||||
@override_settings(QUOTA_SIZES_ASC=[])
|
||||
def test_update_quota_not_in_sizes_asc(self):
|
||||
"""
|
||||
Tests that the quota will still update to a size even if it is not
|
||||
placed in QUOTA_SIZES_ASC
|
||||
"""
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project, }, {user.id: user})
|
||||
setup_mock_caches('RegionOne', project.id)
|
||||
setup_mock_caches('RegionTwo', project.id)
|
||||
|
||||
task = Task.objects.create(
|
||||
ip_address="0.0.0.0", keystone_user={'roles': ['admin']})
|
||||
|
||||
data = {
|
||||
'project_id': 'test_project_id',
|
||||
'size': 'large',
|
||||
'domain_id': 'default',
|
||||
'regions': ['RegionOne', 'RegionTwo'],
|
||||
}
|
||||
|
||||
action = UpdateProjectQuotasAction(data, task=task, order=1)
|
||||
|
||||
action.pre_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
action.post_approve()
|
||||
self.assertEquals(action.valid, True)
|
||||
|
||||
# check the quotas were updated
|
||||
# This relies on test_settings heavily.
|
||||
cinderquota = cinder_cache['RegionOne']['test_project_id']['quota']
|
||||
self.assertEquals(cinderquota['gigabytes'], 50000)
|
||||
novaquota = nova_cache['RegionOne']['test_project_id']['quota']
|
||||
self.assertEquals(novaquota['ram'], 655360)
|
||||
neutronquota = neutron_cache['RegionOne']['test_project_id']['quota']
|
||||
self.assertEquals(neutronquota['network'], 10)
|
||||
|
||||
cinderquota = cinder_cache['RegionTwo']['test_project_id']['quota']
|
||||
self.assertEquals(cinderquota['gigabytes'], 50000)
|
||||
novaquota = nova_cache['RegionTwo']['test_project_id']['quota']
|
||||
self.assertEquals(novaquota['ram'], 655360)
|
||||
neutronquota = neutron_cache['RegionTwo']['test_project_id']['quota']
|
||||
self.assertEquals(neutronquota['network'], 10)
|
||||
|
@ -49,3 +49,5 @@ register_taskview_class(
|
||||
r'^openstack/users/email-update/?$', openstack.UserUpdateEmail)
|
||||
register_taskview_class(
|
||||
r'^openstack/sign-up/?$', openstack.SignUp)
|
||||
register_taskview_class(
|
||||
r'^openstack/quotas/?$', openstack.UpdateProjectQuotas)
|
||||
|
@ -15,13 +15,15 @@
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
from rest_framework.response import Response
|
||||
|
||||
from adjutant.actions import user_store
|
||||
from adjutant.api import models
|
||||
from adjutant.api import utils
|
||||
from adjutant.api.v1 import tasks
|
||||
from adjutant.api.v1.utils import add_task_id_for_roles
|
||||
from adjutant.api.v1.utils import add_task_id_for_roles, create_notification
|
||||
from adjutant.common.quota import QuotaManager
|
||||
|
||||
|
||||
class UserList(tasks.InviteUser):
|
||||
@ -316,3 +318,158 @@ class SignUp(tasks.CreateProject):
|
||||
|
||||
def post(self, request, format=None):
|
||||
return super(SignUp, self).post(request)
|
||||
|
||||
|
||||
class UpdateProjectQuotas(tasks.TaskView):
|
||||
"""
|
||||
The OpenStack endpoint to update the quota of a project in
|
||||
one or more regions
|
||||
"""
|
||||
|
||||
task_type = "update_quota"
|
||||
default_actions = ["UpdateProjectQuotasAction", ]
|
||||
|
||||
_number_of_returned_tasks = 5
|
||||
|
||||
def get_region_quota_data(self, region_id):
|
||||
quota_manager = QuotaManager(self.project_id)
|
||||
current_quota = quota_manager.get_current_region_quota(region_id)
|
||||
current_quota_size = quota_manager.get_quota_size(current_quota)
|
||||
change_options = quota_manager.get_quota_change_options(
|
||||
current_quota_size)
|
||||
current_usage = quota_manager.get_current_usage(region_id)
|
||||
return {"region": region_id,
|
||||
"current_quota": current_quota,
|
||||
"current_quota_size": current_quota_size,
|
||||
"quota_change_options": change_options,
|
||||
"current_usage": current_usage
|
||||
}
|
||||
|
||||
def get_active_quota_tasks(self):
|
||||
# Get the 5 last quota tasks.
|
||||
task_list = models.Task.objects.filter(
|
||||
task_type__exact=self.task_type,
|
||||
project_id__exact=self.project_id,
|
||||
cancelled=0,
|
||||
).order_by('-created_on')[:self._number_of_returned_tasks]
|
||||
|
||||
response_tasks = []
|
||||
|
||||
for task in task_list:
|
||||
status = "Awaiting Approval"
|
||||
if task.completed:
|
||||
status = "Completed"
|
||||
|
||||
task_data = {}
|
||||
for action in task.actions:
|
||||
task_data.update(action.action_data)
|
||||
new_dict = {
|
||||
"id": task.uuid,
|
||||
"regions": task_data['regions'],
|
||||
"size": task_data['size'],
|
||||
"request_user":
|
||||
task.keystone_user['username'],
|
||||
"task_created": task.created_on,
|
||||
"valid": all([a.valid for a in task.actions]),
|
||||
"status": status
|
||||
}
|
||||
response_tasks.append(new_dict)
|
||||
|
||||
return response_tasks
|
||||
|
||||
def check_region_exists(self, region):
|
||||
# Check that the region actually exists
|
||||
id_manager = user_store.IdentityManager()
|
||||
v_region = id_manager.get_region(region)
|
||||
if not v_region:
|
||||
return False
|
||||
return True
|
||||
|
||||
@utils.mod_or_admin
|
||||
def get(self, request):
|
||||
"""
|
||||
This endpoint returns data about what sizes are available
|
||||
as well as the current status of a specified region's quotas.
|
||||
"""
|
||||
|
||||
quota_settings = settings.PROJECT_QUOTA_SIZES
|
||||
size_order = settings.QUOTA_SIZES_ASC
|
||||
|
||||
self.project_id = request.keystone_user['project_id']
|
||||
regions = request.query_params.get('regions', None)
|
||||
|
||||
if regions:
|
||||
regions = regions.split(",")
|
||||
else:
|
||||
id_manager = user_store.IdentityManager()
|
||||
# Only get the region id as that is what will be passed from
|
||||
# parameters otherwise
|
||||
regions = (region.id for region in id_manager.list_regions())
|
||||
|
||||
region_quotas = []
|
||||
|
||||
quota_manager = QuotaManager(self.project_id)
|
||||
for region in regions:
|
||||
if self.check_region_exists(region):
|
||||
region_quotas.append(quota_manager.get_region_quota_data(
|
||||
region))
|
||||
else:
|
||||
return Response(
|
||||
{"ERROR": ['Region: %s is not valid' % region]}, 400)
|
||||
|
||||
response_tasks = self.get_active_quota_tasks()
|
||||
|
||||
return Response({'regions': region_quotas,
|
||||
"quota_sizes": quota_settings,
|
||||
"quota_size_order": size_order,
|
||||
"active_quota_tasks": response_tasks})
|
||||
|
||||
@utils.mod_or_admin
|
||||
def post(self, request):
|
||||
|
||||
request.data['project_id'] = request.keystone_user['project_id']
|
||||
self.project_id = request.keystone_user['project_id']
|
||||
|
||||
regions = request.data.get('regions', None)
|
||||
|
||||
if not regions:
|
||||
id_manager = user_store.IdentityManager()
|
||||
regions = [region.id for region in id_manager.list_regions()]
|
||||
request.data['regions'] = regions
|
||||
|
||||
self.logger.info("(%s) - New UpdateProjectQuotas request."
|
||||
% timezone.now())
|
||||
|
||||
processed, status = self.process_actions(request)
|
||||
|
||||
# check the status
|
||||
errors = processed.get('errors', None)
|
||||
if errors:
|
||||
self.logger.info("(%s) - Validation errors with task." %
|
||||
timezone.now())
|
||||
return Response(errors, status=status)
|
||||
|
||||
if processed.get('auto_approved', False):
|
||||
response_dict = {'notes': processed['notes']}
|
||||
return Response(response_dict, status=status)
|
||||
|
||||
task = processed['task']
|
||||
action_models = task.actions
|
||||
valid = all([act.valid for act in action_models])
|
||||
if not valid:
|
||||
return Response({'errors': ['Actions invalid. You may have usage '
|
||||
'above the new quota level.']}, 400)
|
||||
|
||||
# Action needs to be manually approved
|
||||
notes = {
|
||||
'notes':
|
||||
['New task for UpdateProjectQuotas.']
|
||||
}
|
||||
|
||||
create_notification(processed['task'], notes)
|
||||
self.logger.info("(%s) - Task processed. Awaiting Aprroval"
|
||||
% timezone.now())
|
||||
|
||||
response_dict = {'notes': ['Task processed. Awaiting Aprroval.']}
|
||||
|
||||
return Response(response_dict, status=202)
|
||||
|
6
adjutant/api/v1/templates/quota_completed.txt
Normal file
6
adjutant/api/v1/templates/quota_completed.txt
Normal file
@ -0,0 +1,6 @@
|
||||
This email is to confirm that the quota for your openstack project {{ task.cache.project_id }} has been changed to {{ task.cache.size }}.
|
||||
|
||||
If you did not do this yourself, please get in touch with your systems administrator to report suspicious activity and secure your account.
|
||||
|
||||
Kind regards,
|
||||
The Openstack team
|
@ -39,9 +39,12 @@ def setup_temp_cache(projects, users):
|
||||
users.update({admin_user.id: admin_user})
|
||||
|
||||
region_one = mock.Mock()
|
||||
region_one.id = 'region_id_0'
|
||||
region_one.id = 'RegionOne'
|
||||
region_one.name = 'RegionOne'
|
||||
|
||||
region_two = mock.Mock()
|
||||
region_two.id = 'RegionTwo'
|
||||
|
||||
global temp_cache
|
||||
|
||||
# TODO(adriant): region and project keys are name, should be ID.
|
||||
@ -58,6 +61,7 @@ def setup_temp_cache(projects, users):
|
||||
},
|
||||
'regions': {
|
||||
'RegionOne': region_one,
|
||||
'RegionTwo': region_two
|
||||
},
|
||||
'domains': {
|
||||
default_domain.id: default_domain,
|
||||
@ -267,6 +271,10 @@ class FakeManager(object):
|
||||
global temp_cache
|
||||
return temp_cache['regions'].get(region_id, None)
|
||||
|
||||
def list_regions(self):
|
||||
global temp_cache
|
||||
return temp_cache['regions'].values()
|
||||
|
||||
|
||||
class modify_dict_settings(override_settings):
|
||||
"""
|
||||
|
@ -18,9 +18,17 @@ from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
from adjutant.api.models import Token
|
||||
from adjutant.api.models import Token, Task
|
||||
from adjutant.api.v1.tests import FakeManager, setup_temp_cache
|
||||
from adjutant.actions.v1.tests import (
|
||||
get_fake_neutron, get_fake_novaclient, get_fake_cinderclient,
|
||||
cinder_cache, nova_cache, neutron_cache,
|
||||
setup_mock_caches, setup_quota_cache, FakeResource)
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
@mock.patch('adjutant.actions.user_store.IdentityManager',
|
||||
@ -310,3 +318,766 @@ class OpenstackAPITests(APITestCase):
|
||||
data = {'password': 'testpassword'}
|
||||
response = self.client.post(url, data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
|
||||
@mock.patch(
|
||||
'adjutant.actions.user_store.IdentityManager',
|
||||
FakeManager)
|
||||
@mock.patch(
|
||||
'adjutant.common.quota.get_novaclient',
|
||||
get_fake_novaclient)
|
||||
@mock.patch(
|
||||
'adjutant.common.quota.get_neutronclient',
|
||||
get_fake_neutron)
|
||||
@mock.patch(
|
||||
'adjutant.common.quota.get_cinderclient',
|
||||
get_fake_cinderclient)
|
||||
@mock.patch(
|
||||
'adjutant.actions.v1.resources.' +
|
||||
'openstack_clients.get_neutronclient',
|
||||
get_fake_neutron)
|
||||
@mock.patch(
|
||||
'adjutant.actions.v1.resources.' +
|
||||
'openstack_clients.get_novaclient',
|
||||
get_fake_novaclient)
|
||||
@mock.patch(
|
||||
'adjutant.actions.v1.resources.' +
|
||||
'openstack_clients.get_cinderclient',
|
||||
get_fake_cinderclient)
|
||||
class QuotaAPITests(APITestCase):
|
||||
|
||||
def tearDown(self):
|
||||
""" Clears quota caches """
|
||||
global cinder_cache
|
||||
cinder_cache.clear()
|
||||
global nova_cache
|
||||
nova_cache.clear()
|
||||
global neutron_cache
|
||||
neutron_cache.clear()
|
||||
super(QuotaAPITests, self).tearDown()
|
||||
|
||||
def setUp(self):
|
||||
super(QuotaAPITests, self).setUp()
|
||||
setup_mock_caches('RegionOne', 'test_project_id')
|
||||
setup_mock_caches('RegionTwo', 'test_project_id')
|
||||
|
||||
def check_quota_cache(self, region_name, project_id, size):
|
||||
"""
|
||||
Helper function to check if the global quota caches now match the size
|
||||
defined in the config
|
||||
"""
|
||||
cinderquota = cinder_cache[region_name][project_id]['quota']
|
||||
gigabytes = settings.PROJECT_QUOTA_SIZES[size]['cinder']['gigabytes']
|
||||
self.assertEquals(cinderquota['gigabytes'], gigabytes)
|
||||
|
||||
novaquota = nova_cache[region_name][project_id]['quota']
|
||||
ram = settings.PROJECT_QUOTA_SIZES[size]['nova']['ram']
|
||||
self.assertEquals(novaquota['ram'], ram)
|
||||
|
||||
neutronquota = neutron_cache[region_name][project_id]['quota']
|
||||
network = settings.PROJECT_QUOTA_SIZES[size]['neutron']['network']
|
||||
self.assertEquals(neutronquota['network'], network)
|
||||
|
||||
def test_update_quota_no_history(self):
|
||||
""" Update the quota size of a project with no history """
|
||||
|
||||
admin_headers = {
|
||||
'project_name': "test_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test@example.com",
|
||||
'user_id': "user_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project}, {user.id: user})
|
||||
|
||||
url = "/v1/openstack/quotas/"
|
||||
|
||||
data = {'size': 'medium',
|
||||
'regions': ['RegionOne']}
|
||||
|
||||
response = self.client.post(url, data,
|
||||
headers=admin_headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# Then check to see the quotas have changed
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
|
||||
def test_update_quota_history(self):
|
||||
"""
|
||||
Update the quota size of a project with a quota change recently
|
||||
It should update the quota the first time but wait for admin approval
|
||||
the second time
|
||||
"""
|
||||
admin_headers = {
|
||||
'project_name': "test_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test@example.com",
|
||||
'user_id': "user_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project}, {user.id: user})
|
||||
url = "/v1/openstack/quotas/"
|
||||
|
||||
data = {'size': 'medium',
|
||||
'regions': ['RegionOne']}
|
||||
response = self.client.post(url, data,
|
||||
headers=admin_headers, format='json')
|
||||
# First check we can actually access the page correctly
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# Then check to see the quotas have changed
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
|
||||
data = {'size': 'small',
|
||||
'regions': ['RegionOne']}
|
||||
response = self.client.post(url, data,
|
||||
headers=admin_headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||
|
||||
# Then check to see the quotas have not changed
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
|
||||
# Approve the quota change as admin
|
||||
headers = {
|
||||
'project_name': "admin_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "admin,_member_",
|
||||
'username': "admin",
|
||||
'user_id': "admin_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
# Grab the details for the second task and approve it
|
||||
new_task = Task.objects.all()[1]
|
||||
url = "/v1/tasks/" + new_task.uuid
|
||||
response = self.client.post(url, {'approved': True}, format='json',
|
||||
headers=headers)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
response.data,
|
||||
{'notes': ['Task completed successfully.']}
|
||||
)
|
||||
|
||||
# Quotas should have changed to small
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'small')
|
||||
|
||||
def test_update_quota_old_history(self):
|
||||
"""
|
||||
Update the quota size of a project with a quota change 31 days ago
|
||||
It should update the quota the first time without approval
|
||||
"""
|
||||
|
||||
admin_headers = {
|
||||
'project_name': "test_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test@example.com",
|
||||
'user_id': "user_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project}, {user.id: user})
|
||||
|
||||
url = "/v1/openstack/quotas/"
|
||||
|
||||
data = {'size': 'medium',
|
||||
'regions': ['RegionOne']}
|
||||
response = self.client.post(url, data,
|
||||
headers=admin_headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# Then check to see the quotas have changed
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
|
||||
# Fudge the data to make the task occur 31 days ago
|
||||
task = Task.objects.all()[0]
|
||||
task.completed_on = timezone.now() - timedelta(days=32)
|
||||
task.save()
|
||||
|
||||
data = {'size': 'small',
|
||||
'regions': ['RegionOne']}
|
||||
response = self.client.post(url, data,
|
||||
headers=admin_headers, format='json')
|
||||
# First check we can actually access the page correctly
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# Then check to see the quotas have changed
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'small')
|
||||
|
||||
def test_update_quota_other_project_history(self):
|
||||
"""
|
||||
Tests that a quota update to another project does not interfer
|
||||
with the 30 days per project limit.
|
||||
"""
|
||||
|
||||
headers = {
|
||||
'project_name': "test_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test@example.com",
|
||||
'user_id': "user_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
project2 = mock.Mock()
|
||||
project2.id = 'second_project_id'
|
||||
project2.name = 'second_project'
|
||||
project2.domain = 'default'
|
||||
project2.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project, 'second_project': project2},
|
||||
{user.id: user})
|
||||
setup_mock_caches('RegionOne', 'second_project_id')
|
||||
# setup_quota_cache('RegionOne', 'second_project_id', 'small')
|
||||
|
||||
url = "/v1/openstack/quotas/"
|
||||
|
||||
data = {'size': 'medium',
|
||||
'regions': ['RegionOne']}
|
||||
response = self.client.post(url, data, headers=headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# Then check to see the quotas have changed
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
headers = {
|
||||
'project_name': "second_project",
|
||||
'project_id': "second_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test2@example.com",
|
||||
'user_id': user.id,
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
data = {'regions': ["RegionOne"], 'size': 'medium',
|
||||
'project_id': 'second_project_id'}
|
||||
response = self.client.post(url, data, headers=headers, format='json')
|
||||
# First check we can actually access the page correctly
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# Then check to see the quotas have changed
|
||||
self.check_quota_cache('RegionOne', 'second_project_id', 'medium')
|
||||
|
||||
def test_update_quota_outside_range(self):
|
||||
"""
|
||||
Attempts to update the quota size to a value outside of the
|
||||
project's pre-approved range.
|
||||
"""
|
||||
|
||||
admin_headers = {
|
||||
'project_name': "test_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test@example.com",
|
||||
'user_id': "user_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project}, {user.id: user})
|
||||
|
||||
url = "/v1/openstack/quotas/"
|
||||
|
||||
data = {'size': 'large',
|
||||
'regions': ['RegionOne']}
|
||||
response = self.client.post(url, data,
|
||||
headers=admin_headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||
|
||||
# Then check to see the quotas have not changed (stayed small)
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'small')
|
||||
|
||||
# Approve and test for change
|
||||
|
||||
# Approve the quota change as admin
|
||||
headers = {
|
||||
'project_name': "admin_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "admin,_member_",
|
||||
'username': "admin",
|
||||
'user_id': "admin_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
# Grab the details for the task and approve it
|
||||
new_task = Task.objects.all()[0]
|
||||
url = "/v1/tasks/" + new_task.uuid
|
||||
response = self.client.post(url, {'approved': True}, format='json',
|
||||
headers=headers)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
response.data,
|
||||
{'notes': ['Task completed successfully.']}
|
||||
)
|
||||
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'large')
|
||||
|
||||
def test_calculate_custom_quota_size(self):
|
||||
"""
|
||||
Calculates the best 'fit' quota size from a custom quota.
|
||||
"""
|
||||
|
||||
admin_headers = {
|
||||
'project_name': "test_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test@example.com",
|
||||
'user_id': "user_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project}, {user.id})
|
||||
cinderquota = cinder_cache['RegionOne']['test_project_id']['quota']
|
||||
cinderquota['gigabytes'] = 6000
|
||||
novaquota = nova_cache['RegionOne']['test_project_id']['quota']
|
||||
novaquota['ram'] = 70000
|
||||
neutronquota = neutron_cache['RegionOne']['test_project_id']['quota']
|
||||
neutronquota['network'] = 4
|
||||
|
||||
url = "/v1/openstack/quotas/?regions=RegionOne"
|
||||
|
||||
response = self.client.get(url, headers=admin_headers)
|
||||
# First check we can actually access the page correctly
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
response.data['regions'][0]['current_quota_size'], 'small')
|
||||
|
||||
def test_return_quota_history(self):
|
||||
"""
|
||||
Ensures that the correct quota history and usage data is returned
|
||||
"""
|
||||
|
||||
headers = {
|
||||
'project_name': "test_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test@example.com",
|
||||
'user_id': "user_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project_id': project}, {user.id: user})
|
||||
|
||||
url = "/v1/openstack/quotas/"
|
||||
|
||||
data = {'size': 'large',
|
||||
'regions': ['RegionOne', 'RegionTwo']}
|
||||
response = self.client.post(url, data, headers=headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||
|
||||
response = self.client.get(url, headers=headers)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
recent_task = response.data['active_quota_tasks'][0]
|
||||
self.assertEqual(
|
||||
recent_task['size'], 'large')
|
||||
self.assertEqual(
|
||||
recent_task['request_user'], 'test@example.com')
|
||||
self.assertEqual(
|
||||
recent_task['status'], 'Awaiting Approval')
|
||||
|
||||
def test_set_multi_region_quota(self):
|
||||
""" Sets a quota to all to all regions in a project """
|
||||
|
||||
headers = {
|
||||
'project_name': "test_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test@example.com",
|
||||
'user_id': "user_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project}, {user.id: user})
|
||||
|
||||
url = "/v1/openstack/quotas/"
|
||||
|
||||
data = {'size': 'medium', 'regions': ['RegionOne', 'RegionTwo']}
|
||||
response = self.client.post(url, data, headers=headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
|
||||
self.check_quota_cache('RegionTwo', 'test_project_id', 'medium')
|
||||
|
||||
def test_set_multi_region_quota_history(self):
|
||||
"""
|
||||
Attempts to set a multi region quota with a multi region update history
|
||||
"""
|
||||
|
||||
headers = {
|
||||
'project_name': "test_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test@example.com",
|
||||
'user_id': "user_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project}, {user.id: user})
|
||||
|
||||
url = "/v1/openstack/quotas/"
|
||||
|
||||
data = {'size': 'medium',
|
||||
'regions': ['RegionOne', 'RegionTwo']}
|
||||
response = self.client.post(url, data, headers=headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
|
||||
self.check_quota_cache('RegionTwo', 'test_project_id', 'medium')
|
||||
|
||||
data = {'size': 'small',
|
||||
'project_id': 'test_project_id'}
|
||||
response = self.client.post(url, data, headers=headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||
|
||||
# All of them stay the same
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
|
||||
self.check_quota_cache('RegionTwo', 'test_project_id', 'medium')
|
||||
|
||||
# Approve the task
|
||||
headers = {
|
||||
'project_name': "admin_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "admin,_member_",
|
||||
'username': "admin",
|
||||
'user_id': "admin_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
new_task = Task.objects.all()[1]
|
||||
url = "/v1/tasks/" + new_task.uuid
|
||||
response = self.client.post(url, {'approved': True}, format='json',
|
||||
headers=headers)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
response.data,
|
||||
{'notes': ['Task completed successfully.']}
|
||||
)
|
||||
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'small')
|
||||
|
||||
self.check_quota_cache('RegionTwo', 'test_project_id', 'small')
|
||||
|
||||
def test_set_multi_quota_single_history(self):
|
||||
"""
|
||||
Attempts to set a multi region quota with a single region quota
|
||||
update history
|
||||
"""
|
||||
|
||||
headers = {
|
||||
'project_name': "test_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test@example.com",
|
||||
'user_id': "user_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project}, {user.id: user})
|
||||
|
||||
# Setup custom parts of the quota still within 'small' however
|
||||
|
||||
url = "/v1/openstack/quotas/"
|
||||
|
||||
data = {'size': 'medium',
|
||||
'regions': ['RegionOne']}
|
||||
response = self.client.post(url, data, headers=headers, format='json')
|
||||
# First check we can actually access the page correctly
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
|
||||
url = "/v1/openstack/quotas/"
|
||||
|
||||
data = {'size': 'small',
|
||||
'regions': ['RegionOne', 'RegionTwo']}
|
||||
response = self.client.post(url, data, headers=headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||
|
||||
# Quotas stay the same
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
self.check_quota_cache('RegionTwo', 'test_project_id', 'small')
|
||||
|
||||
headers = {
|
||||
'project_name': "admin_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "admin,_member_",
|
||||
'username': "admin",
|
||||
'user_id': "admin_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
new_task = Task.objects.all()[1]
|
||||
url = "/v1/tasks/" + new_task.uuid
|
||||
|
||||
response = self.client.post(url, {'approved': True}, format='json',
|
||||
headers=headers)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(
|
||||
response.data,
|
||||
{'notes': ['Task completed successfully.']}
|
||||
)
|
||||
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'small')
|
||||
self.check_quota_cache('RegionTwo', 'test_project_id', 'small')
|
||||
|
||||
def test_set_quota_over_limit(self):
|
||||
""" Attempts to set a smaller quota than the current usage """
|
||||
headers = {
|
||||
'project_name': "test_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test@example.com",
|
||||
'user_id': "user_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project}, {user.id: user})
|
||||
setup_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
# Setup current quota as medium
|
||||
# Create a number of lists with limits higher than the small quota
|
||||
|
||||
global nova_cache
|
||||
nova_cache['RegionOne']['test_project_id'][
|
||||
'absolute']["totalInstancesUsed"] = 11
|
||||
|
||||
url = "/v1/openstack/quotas/"
|
||||
|
||||
data = {'size': 'small',
|
||||
'regions': ['RegionOne']}
|
||||
response = self.client.post(url, data,
|
||||
headers=headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
|
||||
data = {'size': 'small',
|
||||
'regions': ['RegionOne']}
|
||||
|
||||
nova_cache['RegionOne']['test_project_id'][
|
||||
'absolute']["totalInstancesUsed"] = 10
|
||||
|
||||
# Test for cinder resources
|
||||
volume_list = [FakeResource(10) for i in range(21)]
|
||||
cinder_cache['RegionOne']['test_project_id']['volumes'] = volume_list
|
||||
|
||||
response = self.client.post(url, data,
|
||||
headers=headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
|
||||
# Test for neutron resources
|
||||
cinder_cache['RegionOne']['test_project_id']['volumes'] = []
|
||||
net_list = [{} for i in range(4)]
|
||||
neutron_cache['RegionOne']['test_project_id']['networks'] = net_list
|
||||
response = self.client.post(url, data,
|
||||
headers=headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'medium')
|
||||
|
||||
# Check that after they are all cleared to sub small levels
|
||||
# the quota updates
|
||||
neutron_cache['RegionOne']['test_project_id']['networks'] = []
|
||||
response = self.client.post(url, data,
|
||||
headers=headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
self.check_quota_cache('RegionOne', 'test_project_id', 'small')
|
||||
|
||||
def test_set_quota_invalid_region(self):
|
||||
""" Attempts to set a quota on a non-existent region """
|
||||
headers = {
|
||||
'project_name': "test_project",
|
||||
'project_id': "test_project_id",
|
||||
'roles': "project_admin,_member_,project_mod",
|
||||
'username': "test@example.com",
|
||||
'user_id': "user_id",
|
||||
'authenticated': True
|
||||
}
|
||||
|
||||
project = mock.Mock()
|
||||
project.id = 'test_project_id'
|
||||
project.name = 'test_project'
|
||||
project.domain = 'default'
|
||||
project.roles = {}
|
||||
|
||||
user = mock.Mock()
|
||||
user.id = 'user_id'
|
||||
user.name = "test@example.com"
|
||||
user.email = "test@example.com"
|
||||
user.domain = 'default'
|
||||
user.password = "test_password"
|
||||
|
||||
setup_temp_cache({'test_project': project}, {user.id: user})
|
||||
|
||||
url = "/v1/openstack/quotas/"
|
||||
|
||||
data = {'size': 'small',
|
||||
'regions': ['RegionThree']}
|
||||
response = self.client.post(url, data,
|
||||
headers=headers, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
@ -781,7 +781,7 @@ class TaskViewTests(AdjutantAPITestCase):
|
||||
self.assertEqual(mail.outbox[0].subject, 'email_update_additional')
|
||||
|
||||
self.assertEqual(mail.outbox[1].to, ['new_test@example.com'])
|
||||
self.assertEqual(mail.outbox[1].subject, 'Your Token')
|
||||
self.assertEqual(mail.outbox[1].subject, 'email_update_token')
|
||||
|
||||
new_token = Token.objects.all()[0]
|
||||
url = "/v1/tokens/" + new_token.token
|
||||
@ -845,7 +845,7 @@ class TaskViewTests(AdjutantAPITestCase):
|
||||
self.assertEqual(mail.outbox[0].subject, 'email_update_additional')
|
||||
|
||||
self.assertEqual(mail.outbox[1].to, ['new_test@example.com'])
|
||||
self.assertEqual(mail.outbox[1].subject, 'Your Token')
|
||||
self.assertEqual(mail.outbox[1].subject, 'email_update_token')
|
||||
|
||||
new_token = Token.objects.all()[0]
|
||||
url = "/v1/tokens/" + new_token.token
|
||||
@ -1170,7 +1170,7 @@ class TaskViewTests(AdjutantAPITestCase):
|
||||
"""
|
||||
|
||||
# NOTE(amelia): sending this email here is probably not the intended
|
||||
# case. It would be more useful in cases such as a quota update or a
|
||||
# case. It would be more useful in utils such as a quota update or a
|
||||
# child project being created that all the project admins should be
|
||||
# notified of
|
||||
|
||||
|
0
adjutant/common/__init__.py
Normal file
0
adjutant/common/__init__.py
Normal file
169
adjutant/common/quota.py
Normal file
169
adjutant/common/quota.py
Normal file
@ -0,0 +1,169 @@
|
||||
# Copyright (C) 2015 Catalyst IT Ltd
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
from adjutant.actions.openstack_clients import (
|
||||
get_novaclient, get_cinderclient, get_neutronclient)
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class QuotaManager(object):
|
||||
"""
|
||||
A manager to allow easier updating and access to quota information
|
||||
across all services.
|
||||
"""
|
||||
|
||||
default_size_diff_threshold = .2
|
||||
|
||||
def __init__(self, project_id, size_difference_threshold=None):
|
||||
self.project_id = project_id
|
||||
self.size_diff_threshold = (size_difference_threshold or
|
||||
self.default_size_diff_threshold)
|
||||
|
||||
def get_current_region_quota(self, region_id):
|
||||
ci_quota = get_cinderclient(region_id) \
|
||||
.quotas.get(self.project_id).to_dict()
|
||||
neutron_quota = get_neutronclient(region_id) \
|
||||
.show_quota(self.project_id)['quota']
|
||||
nova_quota = get_novaclient(region_id) \
|
||||
.quotas.get(self.project_id).to_dict()
|
||||
|
||||
return {'cinder': ci_quota,
|
||||
'nova': nova_quota,
|
||||
'neutron': neutron_quota}
|
||||
|
||||
def get_quota_size(self, current_quota, difference_threshold=None):
|
||||
""" Gets the closest matching quota size for a given quota """
|
||||
quota_differences = {}
|
||||
for size, setting in settings.PROJECT_QUOTA_SIZES.items():
|
||||
match_percentages = []
|
||||
for service_name, values in setting.items():
|
||||
for name, value in values.items():
|
||||
if value != 0:
|
||||
match_percentages.append(
|
||||
float(current_quota[service_name][name]) / value)
|
||||
# Calculate the average of how much it matches the setting
|
||||
difference = abs(
|
||||
(sum(match_percentages) / float(len(match_percentages))) - 1)
|
||||
# TODO(amelia): Nicer form of this due to the new way of doing
|
||||
# per action settings
|
||||
if (difference <= self.size_diff_threshold):
|
||||
quota_differences[size] = difference
|
||||
|
||||
if len(quota_differences) > 0:
|
||||
return min(quota_differences, key=quota_differences.get)
|
||||
# If we don't get a match return custom which means the project will
|
||||
# need admin approval for any change
|
||||
return 'custom'
|
||||
|
||||
def get_quota_change_options(self, quota_size):
|
||||
""" Get's the pre-approved quota change options for a given size """
|
||||
quota_list = settings.QUOTA_SIZES_ASC
|
||||
try:
|
||||
list_position = quota_list.index(quota_size)
|
||||
except ValueError:
|
||||
return []
|
||||
|
||||
quota_change_list = []
|
||||
if list_position - 1 >= 0:
|
||||
quota_change_list.append(quota_list[list_position - 1])
|
||||
if list_position + 1 < len(quota_list):
|
||||
quota_change_list.append(quota_list[list_position + 1])
|
||||
|
||||
return quota_change_list
|
||||
|
||||
def get_region_quota_data(self, region_id):
|
||||
current_quota = self.get_current_region_quota(region_id)
|
||||
current_quota_size = self.get_quota_size(current_quota)
|
||||
change_options = self.get_quota_change_options(current_quota_size)
|
||||
current_usage = self.get_current_usage(region_id)
|
||||
return {'region': region_id,
|
||||
"current_quota": current_quota,
|
||||
"current_quota_size": current_quota_size,
|
||||
"quota_change_options": change_options,
|
||||
"current_usage": current_usage
|
||||
}
|
||||
|
||||
def get_current_usage(self, region_id):
|
||||
cinder_usage = self.get_cinder_usage(region_id)
|
||||
nova_usage = self.get_nova_usage(region_id)
|
||||
neutron_usage = self.get_neutron_usage(region_id)
|
||||
return {'cinder': cinder_usage,
|
||||
'nova': nova_usage,
|
||||
'neutron': neutron_usage}
|
||||
|
||||
def get_cinder_usage(self, region_id):
|
||||
client = get_cinderclient(region_id)
|
||||
|
||||
volumes = client.volumes.list(
|
||||
search_opts={'all_tenants': 1, 'project_id': self.project_id})
|
||||
snapshots = client.volume_snapshots.list(
|
||||
search_opts={'all_tenants': 1, 'project_id': self.project_id})
|
||||
|
||||
# gigabytesUsed should be a total of volumes and snapshots
|
||||
gigabytes = sum([getattr(volume, 'size', 0) for volume
|
||||
in volumes])
|
||||
gigabytes += sum([getattr(snap, 'size', 0) for snap
|
||||
in snapshots])
|
||||
|
||||
return {'gigabytes': gigabytes,
|
||||
'volumes': len(volumes),
|
||||
'snapshots': len(snapshots)
|
||||
}
|
||||
|
||||
def get_neutron_usage(self, region_id):
|
||||
client = get_neutronclient(region_id)
|
||||
|
||||
networks = client.list_networks(
|
||||
tenant_id=self.project_id)['networks']
|
||||
routers = client.list_routers(
|
||||
tenant_id=self.project_id)['routers']
|
||||
floatingips = client.list_floatingips(
|
||||
tenant_id=self.project_id)['floatingips']
|
||||
ports = client.list_ports(
|
||||
tenant_id=self.project_id)['ports']
|
||||
subnets = client.list_subnets(
|
||||
tenant_id=self.project_id)['subnets']
|
||||
security_groups = client.list_security_groups(
|
||||
tenant_id=self.project_id)['security_groups']
|
||||
security_group_rules = client.list_security_group_rules(
|
||||
tenant_id=self.project_id)['security_group_rules']
|
||||
|
||||
return {'network': len(networks),
|
||||
'router': len(routers),
|
||||
'floatingip': len(floatingips),
|
||||
'port': len(ports),
|
||||
'subnet': len(subnets),
|
||||
'secuirty_group': len(security_groups),
|
||||
'security_group_rule': len(security_group_rules)
|
||||
}
|
||||
|
||||
def get_nova_usage(self, region_id):
|
||||
client = get_novaclient(region_id)
|
||||
nova_usage = client.limits.get(
|
||||
tenant_id=self.project_id).to_dict()['absolute']
|
||||
nova_usage_keys = [
|
||||
('instances', 'totalInstancesUsed'),
|
||||
('floating_ips', 'totalFloatingIpsUsed'),
|
||||
('ram', 'totalRAMUsed'),
|
||||
('cores', 'totalCoresUsed'),
|
||||
('secuirty_groups', 'totalSecurityGroupsUsed')
|
||||
]
|
||||
|
||||
nova_usage_dict = {}
|
||||
for key, usage_key in nova_usage_keys:
|
||||
nova_usage_dict[key] = nova_usage[usage_key]
|
||||
|
||||
return nova_usage_dict
|
@ -167,6 +167,8 @@ ROLES_MAPPING = CONFIG['ROLES_MAPPING']
|
||||
|
||||
PROJECT_QUOTA_SIZES = CONFIG.get('PROJECT_QUOTA_SIZES')
|
||||
|
||||
QUOTA_SIZES_ASC = CONFIG.get('QUOTA_SIZES_ASC', [])
|
||||
|
||||
# Defaults for backwards compatibility.
|
||||
ACTIVE_TASKVIEWS = CONFIG.get(
|
||||
'ACTIVE_TASKVIEWS',
|
||||
|
@ -86,7 +86,8 @@ ACTIVE_TASKVIEWS = [
|
||||
'InviteUser',
|
||||
'ResetPassword',
|
||||
'EditUser',
|
||||
'UpdateEmail'
|
||||
'UpdateEmail',
|
||||
'UpdateProjectQuotas',
|
||||
]
|
||||
|
||||
DEFAULT_TASK_SETTINGS = {
|
||||
@ -162,8 +163,8 @@ DEFAULT_ACTION_SETTINGS = {
|
||||
'RegionOne': {
|
||||
'quota_size': 'small'
|
||||
},
|
||||
'RegionTwo': {
|
||||
'quota_size': 'large'
|
||||
'RegionThree': {
|
||||
'quota_size': 'large_cinder_only'
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -183,7 +184,7 @@ DEFAULT_ACTION_SETTINGS = {
|
||||
},
|
||||
'ResetUserPasswordAction': {
|
||||
'blacklisted_roles': ['admin'],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
TASK_SETTINGS = {
|
||||
@ -252,11 +253,23 @@ TASK_SETTINGS = {
|
||||
'update_email': {
|
||||
'emails': {
|
||||
'initial': None,
|
||||
'token': {
|
||||
'subject': 'email_update_token',
|
||||
'template': 'email_update_token.txt'
|
||||
},
|
||||
'completed': {
|
||||
'subject': 'Email Update Complete',
|
||||
'template': 'email_update_completed.txt'
|
||||
}
|
||||
},
|
||||
},
|
||||
'edit_user': {
|
||||
'role_blacklist': ['admin']
|
||||
}
|
||||
},
|
||||
'update_quota': {
|
||||
'duplicate_policy': 'cancel',
|
||||
'days_between_autoapprove': 30,
|
||||
},
|
||||
}
|
||||
|
||||
ROLES_MAPPING = {
|
||||
@ -301,15 +314,76 @@ PROJECT_QUOTA_SIZES = {
|
||||
'subnet': 3,
|
||||
},
|
||||
},
|
||||
'large': {
|
||||
'cinder': {
|
||||
'gigabytes': 73571,
|
||||
'snapshots': 73572,
|
||||
'volumes': 73573,
|
||||
"medium": {
|
||||
"cinder": {
|
||||
"gigabytes": 10000,
|
||||
"volumes": 100,
|
||||
"snapshots": 300
|
||||
},
|
||||
"nova": {
|
||||
"metadata_items": 128,
|
||||
"injected_file_content_bytes": 10240,
|
||||
"ram": 327680,
|
||||
"floating_ips": 25,
|
||||
"key_pairs": 50,
|
||||
"instances": 50,
|
||||
"security_group_rules": 400,
|
||||
"injected_files": 5,
|
||||
"cores": 100,
|
||||
"fixed_ips": 0,
|
||||
"security_groups": 50
|
||||
},
|
||||
"neutron": {
|
||||
"security_group_rule": 400,
|
||||
"subnet": 5,
|
||||
"network": 5,
|
||||
"floatingip": 25,
|
||||
"security_group": 50,
|
||||
"router": 5,
|
||||
"port": 250
|
||||
}
|
||||
},
|
||||
"large": {
|
||||
"cinder": {
|
||||
"gigabytes": 50000,
|
||||
"volumes": 200,
|
||||
"snapshots": 600
|
||||
},
|
||||
"nova": {
|
||||
"metadata_items": 128,
|
||||
"injected_file_content_bytes": 10240,
|
||||
"ram": 655360,
|
||||
"floating_ips": 50,
|
||||
"key_pairs": 50,
|
||||
"instances": 100,
|
||||
"security_group_rules": 800,
|
||||
"injected_files": 5,
|
||||
"cores": 200,
|
||||
"fixed_ips": 0,
|
||||
"security_groups": 100
|
||||
},
|
||||
"neutron": {
|
||||
"security_group_rule": 800,
|
||||
"subnet": 10,
|
||||
"network": 10,
|
||||
"floatingip": 50,
|
||||
"security_group": 100,
|
||||
"router": 10,
|
||||
"port": 500
|
||||
}
|
||||
},
|
||||
"large_cinder_only": {
|
||||
"cinder": {
|
||||
"gigabytes": 50000,
|
||||
"volumes": 200,
|
||||
"snapshots": 600
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
QUOTA_SIZES_ASC = ['small', 'medium', 'large']
|
||||
|
||||
SHOW_ACTION_ENDPOINTS = True
|
||||
|
||||
conf_dict = {
|
||||
@ -330,4 +404,5 @@ conf_dict = {
|
||||
"ROLES_MAPPING": ROLES_MAPPING,
|
||||
"PROJECT_QUOTA_SIZES": PROJECT_QUOTA_SIZES,
|
||||
"SHOW_ACTION_ENDPOINTS": SHOW_ACTION_ENDPOINTS,
|
||||
"QUOTA_SIZES_ASC": QUOTA_SIZES_ASC,
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ ACTIVE_TASKVIEWS:
|
||||
- RoleList
|
||||
- SignUp
|
||||
- UserUpdateEmail
|
||||
- UpdateProjectQuotas
|
||||
|
||||
DEFAULT_TASK_SETTINGS:
|
||||
emails:
|
||||
@ -173,6 +174,8 @@ DEFAULT_ACTION_SETTINGS:
|
||||
regions:
|
||||
RegionOne:
|
||||
quota_size: small
|
||||
UpdateProjectQuotasAction:
|
||||
days_between_autoapprove: 30
|
||||
SendAdditionalEmailAction:
|
||||
initial:
|
||||
email_current_user: false
|
||||
@ -235,13 +238,13 @@ TASK_SETTINGS:
|
||||
emails:
|
||||
- signups@example.com
|
||||
default_region: RegionOne
|
||||
# If 'None' (null in yaml), will default to domain as parent.
|
||||
# If domain isn't set explicity, will service user domain (see KEYSTONE).
|
||||
# If 'None' (null in yaml) will default to domain as parent.
|
||||
# If domain isn't set explicity will service user domain (see KEYSTONE).
|
||||
default_parent_id: null
|
||||
invite_user:
|
||||
duplicate_policy: cancel
|
||||
emails:
|
||||
# To not send this email, set the value to null
|
||||
# To not send this email set the value to null
|
||||
initial: null
|
||||
token:
|
||||
subject: Invitation to an OpenStack project
|
||||
@ -303,6 +306,15 @@ TASK_SETTINGS:
|
||||
subject: OpenStack Email Update Requested
|
||||
template: email_update_started.txt
|
||||
email_current_user: True
|
||||
update_quota:
|
||||
duplicate_policy: cancel
|
||||
size_difference_threshold: 0.1
|
||||
emails:
|
||||
initial: null
|
||||
token: null
|
||||
completed:
|
||||
subject: Openstack Quota updated
|
||||
template: quota_completed.txt
|
||||
|
||||
# mapping between roles and managable roles
|
||||
ROLES_MAPPING:
|
||||
@ -347,3 +359,59 @@ PROJECT_QUOTA_SIZES:
|
||||
security_group: 20
|
||||
security_group_rule: 100
|
||||
subnet: 3
|
||||
medium:
|
||||
cinder:
|
||||
gigabytes: 10000
|
||||
volumes: 100
|
||||
snapshots: 300
|
||||
nova:
|
||||
metadata_items: 128
|
||||
injected_file_content_bytes: 10240
|
||||
ram: 327680
|
||||
floating_ips: 25
|
||||
key_pairs: 50
|
||||
instances: 50
|
||||
security_group_rules: 400
|
||||
injected_files: 5
|
||||
cores: 100
|
||||
fixed_ips: 0
|
||||
security_groups: 50
|
||||
neutron:
|
||||
security_group_rule: 400
|
||||
subnet: 5
|
||||
network: 5
|
||||
floatingip: 25
|
||||
security_group: 50
|
||||
router: 5
|
||||
port: 250
|
||||
large:
|
||||
cinder:
|
||||
gigabytes: 50000
|
||||
volumes: 200
|
||||
snapshots: 600
|
||||
nova:
|
||||
metadata_items: 128
|
||||
injected_file_content_bytes: 10240
|
||||
ram: 655360
|
||||
floating_ips: 50
|
||||
key_pairs: 50
|
||||
instances: 100
|
||||
security_group_rules: 800
|
||||
injected_files: 5
|
||||
cores: 200
|
||||
fixed_ips: 0
|
||||
security_groups: 100
|
||||
neutron:
|
||||
security_group_rule: 800
|
||||
subnet: 10
|
||||
network: 10
|
||||
floatingip: 50
|
||||
security_group: 100
|
||||
router: 10
|
||||
port: 500
|
||||
|
||||
# Ordered list of quota sizes from smallest to biggest
|
||||
QUOTA_SIZES_ASC:
|
||||
- small
|
||||
- medium
|
||||
- large
|
||||
|
Loading…
x
Reference in New Issue
Block a user