summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@yahoo.com>2010-09-09 20:10:31 -0700
committerVishvananda Ishaya <vishvananda@yahoo.com>2010-09-09 20:10:31 -0700
commitc577e91ee3a3eb87a393da2449cab95069a785f4 (patch)
tree24e2cda4db185d5bd73a3e7b64a32800733be56c
parent0aabb8a6febca8d98a750d1bdc78f3160b9684fe (diff)
downloadnova-c577e91ee3a3eb87a393da2449cab95069a785f4.tar.gz
nova-c577e91ee3a3eb87a393da2449cab95069a785f4.tar.xz
nova-c577e91ee3a3eb87a393da2449cab95069a785f4.zip
database support for quotas
-rw-r--r--nova/db/api.py27
-rw-r--r--nova/db/sqlalchemy/api.py43
-rw-r--r--nova/db/sqlalchemy/models.py21
-rw-r--r--nova/endpoint/cloud.py53
-rw-r--r--nova/tests/compute_unittest.py1
-rw-r--r--run_tests.py1
6 files changed, 139 insertions, 7 deletions
diff --git a/nova/db/api.py b/nova/db/api.py
index d81673fad..c22c84768 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -195,6 +195,10 @@ def instance_create(context, values):
return IMPL.instance_create(context, values)
+def instance_data_get_for_project(context, project_id):
+ """Get (instance_count, core_count) for project."""
+ return IMPL.instance_data_get_for_project(context, project_id)
+
def instance_destroy(context, instance_id):
"""Destroy the instance or raise if it does not exist."""
return IMPL.instance_destroy(context, instance_id)
@@ -379,6 +383,29 @@ def export_device_create(context, values):
###################
+def quota_create(context, values):
+ """Create a quota from the values dictionary."""
+ return IMPL.quota_create(context, values)
+
+
+def quota_get(context, project_id):
+ """Retrieve a quota or raise if it does not exist."""
+ return IMPL.quota_get(context, project_id)
+
+
+def quota_update(context, project_id, values):
+ """Update a quota from the values dictionary."""
+ return IMPL.quota_update(context, project_id, values)
+
+
+def quota_destroy(context, project_id):
+ """Destroy the quota or raise if it does not exist."""
+ return IMPL.quota_destroy(context, project_id)
+
+
+###################
+
+
def volume_allocate_shelf_and_blade(context, volume_id):
"""Atomically allocate a free shelf and blade from the pool."""
return IMPL.volume_allocate_shelf_and_blade(context, volume_id)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 4ea7a9071..4b01725ce 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -26,6 +26,7 @@ from nova.db.sqlalchemy import models
from nova.db.sqlalchemy.session import get_session
from sqlalchemy import or_
from sqlalchemy.orm import joinedload_all
+from sqlalchemy.sql import func
FLAGS = flags.FLAGS
@@ -264,6 +265,15 @@ def instance_create(_context, values):
return instance_ref.id
+def instance_data_get_for_project(_context, project_id):
+ session = get_session()
+ return session.query(func.count(models.Instance.id),
+ func.sum(models.Instance.vcpus)
+ ).filter_by(project_id=project_id
+ ).filter_by(deleted=False
+ ).first()
+
+
def instance_destroy(_context, instance_id):
session = get_session()
with session.begin():
@@ -534,6 +544,37 @@ def export_device_create(_context, values):
###################
+def quota_create(_context, values):
+ quota_ref = models.Quota()
+ for (key, value) in values.iteritems():
+ quota_ref[key] = value
+ quota_ref.save()
+ return quota_ref
+
+
+def quota_get(_context, project_id):
+ return models.Quota.find_by_str(project_id)
+
+
+def quota_update(_context, project_id, values):
+ session = get_session()
+ with session.begin():
+ quota_ref = models.Quota.find_by_str(project_id, session=session)
+ for (key, value) in values.iteritems():
+ quota_ref[key] = value
+ quota_ref.save(session=session)
+
+
+def quota_destroy(_context, project_id):
+ session = get_session()
+ with session.begin():
+ quota_ref = models.Quota.find_by_str(project_id, session=session)
+ quota_ref.delete(session=session)
+
+
+###################
+
+
def volume_allocate_shelf_and_blade(_context, volume_id):
session = get_session()
with session.begin():
@@ -621,7 +662,7 @@ def volume_get_instance(_context, volume_id):
def volume_get_shelf_and_blade(_context, volume_id):
session = get_session()
- export_device = session.query(models.ExportDevice
+ export_device = session.query(models.exportdevice
).filter_by(volume_id=volume_id
).first()
if not export_device:
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 2fcade7de..7f510301a 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -222,6 +222,11 @@ class Instance(BASE, NovaBase):
state = Column(Integer)
state_description = Column(String(255))
+ memory_mb = Column(Integer)
+ vcpus = Column(Integer)
+ local_gb = Column(Integer)
+
+
hostname = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id'))
@@ -279,6 +284,22 @@ class Quota(BASE, NovaBase):
gigabytes = Column(Integer)
floating_ips = Column(Integer)
+ @property
+ def str_id(self):
+ return self.project_id
+
+ @classmethod
+ def find_by_str(cls, str_id, session=None, deleted=False):
+ if not session:
+ session = get_session()
+ try:
+ return session.query(cls
+ ).filter_by(project_id=str_id
+ ).filter_by(deleted=deleted
+ ).one()
+ except exc.NoResultFound:
+ new_exc = exception.NotFound("No model for project_id %s" % str_id)
+ raise new_exc.__class__, new_exc, sys.exc_info()[2]
class ExportDevice(BASE, NovaBase):
"""Represates a shelf and blade that a volume can be exported on"""
diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py
index 2866474e6..b8a00075b 100644
--- a/nova/endpoint/cloud.py
+++ b/nova/endpoint/cloud.py
@@ -32,6 +32,7 @@ from twisted.internet import defer
from nova import db
from nova import exception
from nova import flags
+from nova import quota
from nova import rpc
from nova import utils
from nova.auth import rbac
@@ -44,6 +45,11 @@ FLAGS = flags.FLAGS
flags.DECLARE('storage_availability_zone', 'nova.volume.manager')
+class QuotaError(exception.ApiError):
+ """Quota Exceeeded"""
+ pass
+
+
def _gen_key(user_id, key_name):
""" Tuck this into AuthManager """
try:
@@ -276,6 +282,14 @@ class CloudController(object):
@rbac.allow('projectmanager', 'sysadmin')
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['size'] = size
vol['user_id'] = context.user.id
@@ -435,6 +449,12 @@ class CloudController(object):
@rbac.allow('netadmin')
@defer.inlineCallbacks
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)
public_ip = yield rpc.call(network_topic,
{"method": "allocate_floating_ip",
@@ -487,14 +507,30 @@ class CloudController(object):
host = network_ref['host']
if not host:
host = yield rpc.call(FLAGS.network_topic,
- {"method": "set_network_host",
- "args": {"context": None,
- "project_id": context.project.id}})
+ {"method": "set_network_host",
+ "args": {"context": None,
+ "project_id": context.project.id}})
defer.returnValue(db.queue_get_for(context, FLAGS.network_topic, host))
@rbac.allow('projectmanager', 'sysadmin')
@defer.inlineCallbacks
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)
# make sure user can access the image
# vpn image is private so it doesn't show up on lists
vpn = kwargs['image_id'] == FLAGS.vpn_image_id
@@ -516,7 +552,7 @@ class CloudController(object):
images.get(context, kernel_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())
key_data = None
if kwargs.has_key('key_name'):
@@ -540,10 +576,15 @@ class CloudController(object):
base_options['user_id'] = context.user.id
base_options['project_id'] = context.project.id
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['instance_type'] = instance_type
+
+ 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(int(kwargs['max_count'])):
+ for num in range():
inst_id = db.instance_create(context, base_options)
inst = {}
diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py
index 8a7f7b649..b45367eb2 100644
--- a/nova/tests/compute_unittest.py
+++ b/nova/tests/compute_unittest.py
@@ -50,6 +50,7 @@ class ComputeTestCase(test.TrialTestCase):
def tearDown(self): # pylint: disable-msg=C0103
self.manager.delete_user(self.user)
self.manager.delete_project(self.project)
+ super(ComputeTestCase, self).tearDown()
def _create_instance(self):
"""Create a test instance"""
diff --git a/run_tests.py b/run_tests.py
index d5dc5f934..73bf57f97 100644
--- a/run_tests.py
+++ b/run_tests.py
@@ -58,6 +58,7 @@ from nova.tests.flags_unittest import *
from nova.tests.network_unittest import *
from nova.tests.objectstore_unittest import *
from nova.tests.process_unittest import *
+from nova.tests.quota_unittest import *
from nova.tests.rpc_unittest import *
from nova.tests.service_unittest import *
from nova.tests.validator_unittest import *