merged quotas

This commit is contained in:
Vishvananda Ishaya
2010-09-11 18:45:35 -07:00
6 changed files with 296 additions and 4 deletions

View File

@@ -304,7 +304,10 @@ class APIRequestHandler(tornado.web.RequestHandler):
try: try:
failure.raiseException() failure.raiseException()
except exception.ApiError as ex: except exception.ApiError as ex:
self._error(type(ex).__name__ + "." + ex.code, ex.message) if ex.code:
self._error(ex.code, ex.message)
else:
self._error(type(ex).__name__, ex.message)
# TODO(vish): do something more useful with unknown exceptions # TODO(vish): do something more useful with unknown exceptions
except Exception as ex: except Exception as ex:
self._error(type(ex).__name__, str(ex)) self._error(type(ex).__name__, str(ex))

View File

@@ -32,6 +32,7 @@ from twisted.internet import defer
from nova import db from nova import db
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import quota
from nova import rpc from nova import rpc
from nova import utils from nova import utils
from nova.auth import rbac from nova.auth import rbac
@@ -44,6 +45,11 @@ FLAGS = flags.FLAGS
flags.DECLARE('storage_availability_zone', 'nova.volume.manager') flags.DECLARE('storage_availability_zone', 'nova.volume.manager')
class QuotaError(exception.ApiError):
"""Quota Exceeeded"""
pass
def _gen_key(user_id, key_name): def _gen_key(user_id, key_name):
""" Tuck this into AuthManager """ """ Tuck this into AuthManager """
try: try:
@@ -277,6 +283,14 @@ class CloudController(object):
@rbac.allow('projectmanager', 'sysadmin') @rbac.allow('projectmanager', 'sysadmin')
def create_volume(self, context, size, **kwargs): def create_volume(self, context, size, **kwargs):
# check quota
size = int(size)
if quota.allowed_volumes(context, 1, size) < 1:
logging.warn("Quota exceeeded for %s, tried to create %sG volume",
context.project.id, size)
raise QuotaError("Volume quota exceeded. You cannot "
"create a volume of size %s" %
size)
vol = {} vol = {}
vol['size'] = size vol['size'] = size
vol['user_id'] = context.user.id vol['user_id'] = context.user.id
@@ -441,6 +455,12 @@ class CloudController(object):
@rbac.allow('netadmin') @rbac.allow('netadmin')
@defer.inlineCallbacks @defer.inlineCallbacks
def allocate_address(self, context, **kwargs): def allocate_address(self, context, **kwargs):
# check quota
if quota.allowed_floating_ips(context, 1) < 1:
logging.warn("Quota exceeeded for %s, tried to allocate address",
context.project.id)
raise QuotaError("Address quota exceeded. You cannot "
"allocate any more addresses")
network_topic = yield self._get_network_topic(context) network_topic = yield self._get_network_topic(context)
public_ip = yield rpc.call(network_topic, public_ip = yield rpc.call(network_topic,
{"method": "allocate_floating_ip", {"method": "allocate_floating_ip",
@@ -500,6 +520,22 @@ class CloudController(object):
@rbac.allow('projectmanager', 'sysadmin') @rbac.allow('projectmanager', 'sysadmin')
@defer.inlineCallbacks @defer.inlineCallbacks
def run_instances(self, context, **kwargs): def run_instances(self, context, **kwargs):
instance_type = kwargs.get('instance_type', 'm1.small')
if instance_type not in INSTANCE_TYPES:
raise exception.ApiError("Unknown instance type: %s",
instance_type)
# check quota
max_instances = int(kwargs.get('max_count', 1))
min_instances = int(kwargs.get('min_count', max_instances))
num_instances = quota.allowed_instances(context,
max_instances,
instance_type)
if num_instances < min_instances:
logging.warn("Quota exceeeded for %s, tried to run %s instances",
context.project.id, min_instances)
raise QuotaError("Instance quota exceeded. You can only "
"run %s more instances of this type." %
num_instances, "InstanceLimitExceeded")
# make sure user can access the image # make sure user can access the image
# vpn image is private so it doesn't show up on lists # vpn image is private so it doesn't show up on lists
vpn = kwargs['image_id'] == FLAGS.vpn_image_id vpn = kwargs['image_id'] == FLAGS.vpn_image_id
@@ -521,7 +557,7 @@ class CloudController(object):
images.get(context, kernel_id) images.get(context, kernel_id)
images.get(context, ramdisk_id) images.get(context, ramdisk_id)
logging.debug("Going to run instances...") logging.debug("Going to run %s instances...", num_instances)
launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
key_data = None key_data = None
if kwargs.has_key('key_name'): if kwargs.has_key('key_name'):
@@ -546,10 +582,15 @@ class CloudController(object):
base_options['user_id'] = context.user.id base_options['user_id'] = context.user.id
base_options['project_id'] = context.project.id base_options['project_id'] = context.project.id
base_options['user_data'] = kwargs.get('user_data', '') base_options['user_data'] = kwargs.get('user_data', '')
base_options['instance_type'] = kwargs.get('instance_type', 'm1.small')
base_options['security_group'] = security_group base_options['security_group'] = security_group
base_options['instance_type'] = instance_type
for num in range(int(kwargs['max_count'])): type_data = INSTANCE_TYPES[instance_type]
base_options['memory_mb'] = type_data['memory_mb']
base_options['vcpus'] = type_data['vcpus']
base_options['local_gb'] = type_data['local_gb']
for num in range(num_instances):
instance_ref = db.instance_create(context, base_options) instance_ref = db.instance_create(context, base_options)
inst_id = instance_ref['id'] inst_id = instance_ref['id']

91
nova/quota.py Normal file
View File

@@ -0,0 +1,91 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# 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.
"""
Quotas for instances, volumes, and floating ips
"""
from nova import db
from nova import exception
from nova import flags
from nova.compute import instance_types
FLAGS = flags.FLAGS
flags.DEFINE_integer('quota_instances', 10,
'number of instances allowed per project')
flags.DEFINE_integer('quota_cores', 20,
'number of instance cores allowed per project')
flags.DEFINE_integer('quota_volumes', 10,
'number of volumes allowed per project')
flags.DEFINE_integer('quota_gigabytes', 1000,
'number of volume gigabytes allowed per project')
flags.DEFINE_integer('quota_floating_ips', 10,
'number of floating ips allowed per project')
def _get_quota(context, project_id):
rval = {'instances': FLAGS.quota_instances,
'cores': FLAGS.quota_cores,
'volumes': FLAGS.quota_volumes,
'gigabytes': FLAGS.quota_gigabytes,
'floating_ips': FLAGS.quota_floating_ips}
try:
quota = db.quota_get(context, project_id)
for key in rval.keys():
if quota[key] is not None:
rval[key] = quota[key]
except exception.NotFound:
pass
return rval
def allowed_instances(context, num_instances, instance_type):
"""Check quota and return min(num_instances, allowed_instances)"""
project_id = context.project.id
used_instances, used_cores = db.instance_data_get_for_project(context,
project_id)
quota = _get_quota(context, project_id)
allowed_instances = quota['instances'] - used_instances
allowed_cores = quota['cores'] - used_cores
type_cores = instance_types.INSTANCE_TYPES[instance_type]['vcpus']
num_cores = num_instances * type_cores
allowed_instances = min(allowed_instances,
int(allowed_cores // type_cores))
return min(num_instances, allowed_instances)
def allowed_volumes(context, num_volumes, size):
"""Check quota and return min(num_volumes, allowed_volumes)"""
project_id = context.project.id
used_volumes, used_gigabytes = db.volume_data_get_for_project(context,
project_id)
quota = _get_quota(context, project_id)
allowed_volumes = quota['volumes'] - used_volumes
allowed_gigabytes = quota['gigabytes'] - used_gigabytes
num_gigabytes = num_volumes * size
allowed_volumes = min(allowed_volumes,
int(allowed_gigabytes // size))
return min(num_volumes, allowed_volumes)
def allowed_floating_ips(context, num_floating_ips):
"""Check quota and return min(num_floating_ips, allowed_floating_ips)"""
project_id = context.project.id
used_floating_ips = db.floating_ip_count_by_project(context, project_id)
quota = _get_quota(context, project_id)
allowed_floating_ips = quota['floating_ips'] - used_floating_ips
return min(num_floating_ips, allowed_floating_ips)

View File

@@ -50,6 +50,7 @@ class ComputeTestCase(test.TrialTestCase):
def tearDown(self): # pylint: disable-msg=C0103 def tearDown(self): # pylint: disable-msg=C0103
self.manager.delete_user(self.user) self.manager.delete_user(self.user)
self.manager.delete_project(self.project) self.manager.delete_project(self.project)
super(ComputeTestCase, self).tearDown()
def _create_instance(self): def _create_instance(self):
"""Create a test instance""" """Create a test instance"""

View File

@@ -0,0 +1,155 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# 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.
import logging
from nova import db
from nova import exception
from nova import flags
from nova import quota
from nova import test
from nova import utils
from nova.auth import manager
from nova.endpoint import cloud
from nova.endpoint import api
FLAGS = flags.FLAGS
class QuotaTestCase(test.TrialTestCase):
def setUp(self): # pylint: disable-msg=C0103
logging.getLogger().setLevel(logging.DEBUG)
super(QuotaTestCase, self).setUp()
self.flags(connection_type='fake',
quota_instances=2,
quota_cores=4,
quota_volumes=2,
quota_gigabytes=20,
quota_floating_ips=1)
self.cloud = cloud.CloudController()
self.manager = manager.AuthManager()
self.user = self.manager.create_user('admin', 'admin', 'admin', True)
self.project = self.manager.create_project('admin', 'admin', 'admin')
self.network = utils.import_object(FLAGS.network_manager)
self.context = api.APIRequestContext(handler=None,
project=self.project,
user=self.user)
def tearDown(self): # pylint: disable-msg=C0103
manager.AuthManager().delete_project(self.project)
manager.AuthManager().delete_user(self.user)
super(QuotaTestCase, self).tearDown()
def _create_instance(self, cores=2):
"""Create a test instance"""
inst = {}
inst['image_id'] = 'ami-test'
inst['reservation_id'] = 'r-fakeres'
inst['user_id'] = self.user.id
inst['project_id'] = self.project.id
inst['instance_type'] = 'm1.large'
inst['vcpus'] = cores
inst['mac_address'] = utils.generate_mac()
return db.instance_create(self.context, inst)['id']
def _create_volume(self, size=10):
"""Create a test volume"""
vol = {}
vol['user_id'] = self.user.id
vol['project_id'] = self.project.id
vol['size'] = size
return db.volume_create(self.context, vol)['id']
def test_quota_overrides(self):
"""Make sure overriding a projects quotas works"""
num_instances = quota.allowed_instances(self.context, 100, 'm1.small')
self.assertEqual(num_instances, 2)
db.quota_create(self.context, {'project_id': self.project.id,
'instances': 10})
num_instances = quota.allowed_instances(self.context, 100, 'm1.small')
self.assertEqual(num_instances, 4)
db.quota_update(self.context, self.project.id, {'cores': 100})
num_instances = quota.allowed_instances(self.context, 100, 'm1.small')
self.assertEqual(num_instances, 10)
db.quota_destroy(self.context, self.project.id)
def test_too_many_instances(self):
instance_ids = []
for i in range(FLAGS.quota_instances):
instance_id = self._create_instance()
instance_ids.append(instance_id)
self.assertFailure(self.cloud.run_instances(self.context,
min_count=1,
max_count=1,
instance_type='m1.small'),
cloud.QuotaError)
for instance_id in instance_ids:
db.instance_destroy(self.context, instance_id)
def test_too_many_cores(self):
instance_ids = []
instance_id = self._create_instance(cores=4)
instance_ids.append(instance_id)
self.assertFailure(self.cloud.run_instances(self.context,
min_count=1,
max_count=1,
instance_type='m1.small'),
cloud.QuotaError)
for instance_id in instance_ids:
db.instance_destroy(self.context, instance_id)
def test_too_many_volumes(self):
volume_ids = []
for i in range(FLAGS.quota_volumes):
volume_id = self._create_volume()
volume_ids.append(volume_id)
self.assertRaises(cloud.QuotaError,
self.cloud.create_volume,
self.context,
size=10)
for volume_id in volume_ids:
db.volume_destroy(self.context, volume_id)
def test_too_many_gigabytes(self):
volume_ids = []
volume_id = self._create_volume(size=20)
volume_ids.append(volume_id)
self.assertRaises(cloud.QuotaError,
self.cloud.create_volume,
self.context,
size=10)
for volume_id in volume_ids:
db.volume_destroy(self.context, volume_id)
def test_too_many_addresses(self):
address = '192.168.0.100'
try:
db.floating_ip_get_by_address(None, address)
except exception.NotFound:
db.floating_ip_create(None, {'address': address,
'host': FLAGS.host})
float_addr = self.network.allocate_floating_ip(self.context,
self.project.id)
# NOTE(vish): This assert never fails. When cloud attempts to
# make an rpc.call, the test just finishes with OK. It
# appears to be something in the magic inline callbacks
# that is breaking.
self.assertFailure(self.cloud.allocate_address(self.context),
cloud.QuotaError)

View File

@@ -58,6 +58,7 @@ from nova.tests.flags_unittest import *
from nova.tests.network_unittest import * from nova.tests.network_unittest import *
from nova.tests.objectstore_unittest import * from nova.tests.objectstore_unittest import *
from nova.tests.process_unittest import * from nova.tests.process_unittest import *
from nova.tests.quota_unittest import *
from nova.tests.rpc_unittest import * from nova.tests.rpc_unittest import *
from nova.tests.scheduler_unittest import * from nova.tests.scheduler_unittest import *
from nova.tests.service_unittest import * from nova.tests.service_unittest import *