diff --git a/etc/reddwarf/reddwarf.conf.sample b/etc/reddwarf/reddwarf.conf.sample index ceb8f37cd1..264956d03c 100644 --- a/etc/reddwarf/reddwarf.conf.sample +++ b/etc/reddwarf/reddwarf.conf.sample @@ -50,6 +50,7 @@ block_device_mapping = /var/lib/mysql device_path = /var/lib/mysql mount_point = /var/lib/mysql max_accepted_volume_size = 10 +max_instances_per_user = 5 volume_time_out=30 # Reddwarf DNS diff --git a/etc/reddwarf/reddwarf.conf.test b/etc/reddwarf/reddwarf.conf.test index 07a530f168..2fff24240d 100644 --- a/etc/reddwarf/reddwarf.conf.test +++ b/etc/reddwarf/reddwarf.conf.test @@ -61,7 +61,8 @@ nova_volume_service_type = volume nova_volume_service_name = Volume Service device_path = /dev/vdb mount_point = /var/lib/mysql -max_accepted_volume_size = 10 +max_accepted_volume_size = 25 +max_instances_per_user = 5 volume_time_out=30 # Auth diff --git a/reddwarf/common/exception.py b/reddwarf/common/exception.py index c790960411..bb7e20d401 100644 --- a/reddwarf/common/exception.py +++ b/reddwarf/common/exception.py @@ -82,6 +82,16 @@ class OverLimit(ReddwarfError): "rate.") +class QuotaExceeded(ReddwarfError): + + message = _("User instance quota exceeded.") + + +class VolumeQuotaExceeded(QuotaExceeded): + + message = _("Instance volume quota exceeded.") + + class GuestError(ReddwarfError): message = _("An error occurred communicating with the guest: " diff --git a/reddwarf/instance/service.py b/reddwarf/instance/service.py index c36b398c2b..a58fb71fff 100644 --- a/reddwarf/instance/service.py +++ b/reddwarf/instance/service.py @@ -56,6 +56,8 @@ class BaseController(wsgi.Controller): ], webob.exc.HTTPRequestEntityTooLarge: [ exception.OverLimit, + exception.QuotaExceeded, + exception.VolumeQuotaExceeded, ], webob.exc.HTTPServerError: [ exception.VolumeCreationFailure @@ -246,6 +248,17 @@ class InstanceController(BaseController): raise exception.BadValue(msg=e) else: volume_size = None + + instance_max = int(config.Config.get('max_instances_per_user', 5)) + number_instances = models.DBInstance.find_all(tenant_id=tenant_id, + deleted=False).count() + + if number_instances >= instance_max: + # That's too many, pal. Got to cut you off. + LOG.error(_("New instance would exceed user instance quota.")) + msg = "User instance quota of %d would be exceeded." + raise exception.QuotaExceeded(msg % instance_max) + instance = models.Instance.create(context, name, flavor_id, image_id, databases, users, service_type, volume_size) @@ -285,18 +298,18 @@ class InstanceController(BaseController): "integer value, %s cannot be accepted." % volume_size) raise exception.ReddwarfError(msg) - max_size = int(config.Config.get('max_accepted_volume_size', - 1)) + max_size = int(config.Config.get('max_accepted_volume_size', 1)) if int(volume_size) > max_size: msg = ("Volume 'size' cannot exceed maximum " "of %d Gb, %s cannot be accepted." % (max_size, volume_size)) - raise exception.ReddwarfError(msg) + raise exception.VolumeQuotaExceeded(msg) @staticmethod def _validate(body): """Validate that the request has all the required parameters""" InstanceController._validate_body_not_empty(body) + try: body['instance'] body['instance']['flavorRef'] @@ -315,6 +328,7 @@ class InstanceController(BaseController): raise exception.MissingKey(key="size") elif must_have_vol: raise exception.MissingKey(key="volume") + except KeyError as e: LOG.error(_("Create Instance Required field(s) - %s") % e) raise exception.ReddwarfError("Required element/key - %s " diff --git a/reddwarf/taskmanager/api.py b/reddwarf/taskmanager/api.py index 9b11d592ce..3c18173651 100644 --- a/reddwarf/taskmanager/api.py +++ b/reddwarf/taskmanager/api.py @@ -59,6 +59,7 @@ class API(object): logging.error("Error running async task:") logging.error((traceback.format_exception(type_, value, tb))) raise type_, value, tb + eventlet.spawn_after(0, func) def _get_routing_key(self): diff --git a/reddwarf/taskmanager/manager.py b/reddwarf/taskmanager/manager.py index de3844c91d..747d0182b0 100644 --- a/reddwarf/taskmanager/manager.py +++ b/reddwarf/taskmanager/manager.py @@ -80,4 +80,3 @@ class TaskManager(service.Manager): instance_tasks.create_instance(flavor_id, flavor_ram, image_id, databases, users, service_type, volume_size) - diff --git a/reddwarf/taskmanager/models.py b/reddwarf/taskmanager/models.py index b53e2a3b8d..e2f9483490 100644 --- a/reddwarf/taskmanager/models.py +++ b/reddwarf/taskmanager/models.py @@ -148,7 +148,8 @@ class FreshInstanceTasks(FreshInstance): if utils.bool_from_string(dns_support): def get_server(): - return nova_client.servers.get(self.db_info.compute_instance_id) + c_id = self.db_info.compute_instance_id + return nova_client.servers.get(c_id) def ip_is_available(server): LOG.info("Polling for ip addresses: $%s " % server.addresses) @@ -200,7 +201,7 @@ class BuiltInstanceTasks(BuiltInstance): dns_api.delete_instance_entry(instance_id=self.db_info.id) except Exception as ex: LOG.error("Error during dns entry for instance %s " - % self.db_info.id ) + % self.db_info.id) LOG.error(ex) def resize_volume(self, new_size): diff --git a/rsdns/client/__init__.py b/rsdns/client/__init__.py index 60285e745e..53e53633de 100644 --- a/rsdns/client/__init__.py +++ b/rsdns/client/__init__.py @@ -21,4 +21,3 @@ from rsdns.client.dns_client import DNSaas from rsdns.client.dns_client import DNSaasClient from rsdns.client.domains import DomainsManager from rsdns.client.records import RecordsManager - diff --git a/rsdns/client/dns_client.py b/rsdns/client/dns_client.py index e86cfbd906..a43511636a 100644 --- a/rsdns/client/dns_client.py +++ b/rsdns/client/dns_client.py @@ -38,7 +38,7 @@ LOG = logging.getLogger('rsdns.client.dns_client') class DNSaasClient(HTTPClient): def __init__(self, accountId, user, apikey, auth_url, management_base_url): - tenant = "dbaas" + tenant = "dbaas" super(DNSaasClient, self).__init__(user, apikey, tenant, auth_url) self.accountId = accountId self.management_base_url = management_base_url @@ -48,7 +48,8 @@ class DNSaasClient(HTTPClient): def authenticate(self): """Set the management url and auth token""" - req_body = {'credentials':{'username':self.user, 'key':self.api_key}} + req_body = {'credentials': {'username': self.user, + 'key': self.api_key}} resp, body = self.request(self.auth_url, "POST", body=req_body) if 'access' in body: if not self.management_url: @@ -103,6 +104,7 @@ class DNSaasClient(HTTPClient): return resp, body + class DNSaas(Client): """ Top-level object to access the DNSaas service @@ -115,8 +117,9 @@ class DNSaas(Client): from rsdns.client.domains import DomainsManager from rsdns.client.records import RecordsManager - super(DNSaas, self).__init__(self, accountId, username, apikey, auth_url, management_base_url) + super(DNSaas, self).__init__(self, accountId, username, apikey, + auth_url, management_base_url) self.client = DNSaasClient(accountId, username, apikey, auth_url, management_base_url) self.domains = DomainsManager(self) - self.records = RecordsManager(self) \ No newline at end of file + self.records = RecordsManager(self) diff --git a/rsdns/client/domains.py b/rsdns/client/domains.py index ea26cd5f63..f2f85abc0c 100644 --- a/rsdns/client/domains.py +++ b/rsdns/client/domains.py @@ -31,6 +31,7 @@ class Domain(base.Resource): def response_list_name(self): return "domains" + class FutureDomain(FutureResource): def convert_callback(self, resp, body): diff --git a/rsdns/client/future.py b/rsdns/client/future.py index 5d4e85f4f2..170b89bf5b 100644 --- a/rsdns/client/future.py +++ b/rsdns/client/future.py @@ -1,4 +1,5 @@ + class RsDnsError(RuntimeError): def __init__(self, error): @@ -13,9 +14,10 @@ class RsDnsError(RuntimeError): def __str__(self): return self.message + class FutureResource(object): """Polls a callback url to return a resource.""" - + def __init__(self, manager, jobId, callbackUrl, status, **kwargs): self.manager = manager self.jobId = jobId @@ -41,8 +43,6 @@ class FutureResource(object): return None resp_list = body['response'][self.response_list_name()] self.result = self.manager.create_from_list(resp_list) - #self.resource_class(self, res) for res in list] - #self.result = Domain(self.manager, body['self.convert_callback(resp, body) return self.result @property diff --git a/rsdns/client/records.py b/rsdns/client/records.py index df90170b57..d1f49a0b28 100644 --- a/rsdns/client/records.py +++ b/rsdns/client/records.py @@ -53,7 +53,8 @@ class RecordsManager(base.ManagerWithFind): """ resource_class = Record - def create(self, domain, record_name, record_data, record_type, record_ttl): + def create(self, domain, record_name, record_data, record_type, + record_ttl): """ Create a new Record on the given domain @@ -61,14 +62,14 @@ class RecordsManager(base.ManagerWithFind): :param record: The ID of the :class:`Record` to get. :rtype: :class:`Record` """ - data = {"records":[{"type": record_type, "name": record_name, - "data": record_data, "ttl": record_ttl }]} + data = {"records": [{"type": record_type, "name": record_name, + "data": record_data, "ttl": record_ttl}]} resp, body = self.api.client.post("/domains/%s/records" % \ base.getid(domain), body=data) if resp.status == 202: return FutureRecord(self, **body) - raise RuntimeError("Did not expect response when creating a DNS record " - "%s" % str(resp.status)) + raise RuntimeError("Did not expect response when creating a DNS " + "record %s" % str(resp.status)) def create_from_list(self, list): return [self.resource_class(self, res) for res in list] @@ -143,4 +144,3 @@ class RecordsManager(base.ManagerWithFind): else: raise RuntimeError("Next href had multiple offset params!") return (list, next_offset) -