From 650ff9387cdd524c684128a3579ea0f6cf115bcf Mon Sep 17 00:00:00 2001 From: Mark Doffman Date: Wed, 15 Jun 2016 10:09:19 -0500 Subject: [PATCH] Add quota related tables to the api database. Quotas are required to exist in the API database as we need to enforce quotas across cells. blueprint cells-quota-api-db Change-Id: I52fd680eaa4880b06f7f8d4bd1bb74920e73195d --- .../migrate_repo/versions/027_quotas.py | 124 ++++++++++++++++++ nova/db/sqlalchemy/api_models.py | 119 +++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 nova/db/sqlalchemy/api_migrations/migrate_repo/versions/027_quotas.py diff --git a/nova/db/sqlalchemy/api_migrations/migrate_repo/versions/027_quotas.py b/nova/db/sqlalchemy/api_migrations/migrate_repo/versions/027_quotas.py new file mode 100644 index 000000000..8c95143c0 --- /dev/null +++ b/nova/db/sqlalchemy/api_migrations/migrate_repo/versions/027_quotas.py @@ -0,0 +1,124 @@ +# 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. +"""API Database migrations for quotas""" + +from migrate import UniqueConstraint +from sqlalchemy import Column +from sqlalchemy import DateTime +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import Integer +from sqlalchemy import MetaData +from sqlalchemy import String +from sqlalchemy import Table + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + quota_classes = Table('quota_classes', meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('id', Integer, primary_key=True, nullable=False), + Column('class_name', String(length=255)), + Column('resource', String(length=255)), + Column('hard_limit', Integer), + Index('quota_classes_class_name_idx', 'class_name'), + mysql_engine='InnoDB', + mysql_charset='utf8' + ) + + quota_classes.create(checkfirst=True) + + quota_usages = Table('quota_usages', meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('id', Integer, primary_key=True, nullable=False), + Column('project_id', String(length=255)), + Column('resource', String(length=255), nullable=False), + Column('in_use', Integer, nullable=False), + Column('reserved', Integer, nullable=False), + Column('until_refresh', Integer), + Column('user_id', String(length=255)), + Index('quota_usages_project_id_idx', 'project_id'), + Index('quota_usages_user_id_idx', 'user_id'), + mysql_engine='InnoDB', + mysql_charset='utf8' + ) + + quota_usages.create(checkfirst=True) + + quotas = Table('quotas', meta, + Column('id', Integer, primary_key=True, nullable=False), + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('project_id', String(length=255)), + Column('resource', String(length=255), nullable=False), + Column('hard_limit', Integer), + UniqueConstraint('project_id', 'resource', + name='uniq_quotas0project_id0resource'), + mysql_engine='InnoDB', + mysql_charset='utf8' + ) + + quotas.create(checkfirst=True) + + uniq_name = "uniq_project_user_quotas0user_id0project_id0resource" + project_user_quotas = Table('project_user_quotas', meta, + Column('id', Integer, primary_key=True, + nullable=False), + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('user_id', + String(length=255), + nullable=False), + Column('project_id', + String(length=255), + nullable=False), + Column('resource', + String(length=255), + nullable=False), + Column('hard_limit', Integer, nullable=True), + UniqueConstraint('user_id', 'project_id', 'resource', + name=uniq_name), + Index('project_user_quotas_project_id_idx', + 'project_id'), + Index('project_user_quotas_user_id_idx', + 'user_id'), + mysql_engine='InnoDB', + mysql_charset='utf8', + ) + + project_user_quotas.create(checkfirst=True) + + reservations = Table('reservations', meta, + Column('created_at', DateTime), + Column('updated_at', DateTime), + Column('id', Integer, primary_key=True, nullable=False), + Column('uuid', String(length=36), nullable=False), + Column('usage_id', Integer, ForeignKey('quota_usages.id'), + nullable=False), + Column('project_id', String(length=255)), + Column('resource', String(length=255)), + Column('delta', Integer, nullable=False), + Column('expire', DateTime), + Column('user_id', String(length=255)), + Index('reservations_project_id_idx', 'project_id'), + Index('reservations_uuid_idx', 'uuid'), + Index('reservations_expire_idx', 'expire'), + Index('reservations_user_id_idx', 'user_id'), + mysql_engine='InnoDB', + mysql_charset='utf8' + ) + + reservations.create(checkfirst=True) diff --git a/nova/db/sqlalchemy/api_models.py b/nova/db/sqlalchemy/api_models.py index 9b1248397..970e7b6a4 100644 --- a/nova/db/sqlalchemy/api_models.py +++ b/nova/db/sqlalchemy/api_models.py @@ -14,6 +14,7 @@ from oslo_db.sqlalchemy import models from sqlalchemy import Boolean from sqlalchemy import Column +from sqlalchemy import DateTime from sqlalchemy.dialects.mysql import MEDIUMTEXT from sqlalchemy import Enum from sqlalchemy.ext.declarative import declarative_base @@ -423,3 +424,121 @@ class InstanceGroup(API_BASE): @property def members(self): return [m.instance_uuid for m in self._members] + + +class Quota(API_BASE): + """Represents a single quota override for a project. + + If there is no row for a given project id and resource, then the + default for the quota class is used. If there is no row for a + given quota class and resource, then the default for the + deployment is used. If the row is present but the hard limit is + Null, then the resource is unlimited. + """ + + __tablename__ = 'quotas' + __table_args__ = ( + schema.UniqueConstraint("project_id", "resource", + name="uniq_quotas0project_id0resource" + ), + ) + id = Column(Integer, primary_key=True) + + project_id = Column(String(255)) + + resource = Column(String(255), nullable=False) + hard_limit = Column(Integer) + + +class ProjectUserQuota(API_BASE): + """Represents a single quota override for a user with in a project.""" + + __tablename__ = 'project_user_quotas' + uniq_name = "uniq_project_user_quotas0user_id0project_id0resource" + __table_args__ = ( + schema.UniqueConstraint("user_id", "project_id", "resource", + name=uniq_name), + Index('project_user_quotas_project_id_idx', + 'project_id'), + Index('project_user_quotas_user_id_idx', + 'user_id',) + ) + id = Column(Integer, primary_key=True, nullable=False) + + project_id = Column(String(255), nullable=False) + user_id = Column(String(255), nullable=False) + + resource = Column(String(255), nullable=False) + hard_limit = Column(Integer) + + +class QuotaClass(API_BASE): + """Represents a single quota override for a quota class. + + If there is no row for a given quota class and resource, then the + default for the deployment is used. If the row is present but the + hard limit is Null, then the resource is unlimited. + """ + + __tablename__ = 'quota_classes' + __table_args__ = ( + Index('quota_classes_class_name_idx', 'class_name'), + ) + id = Column(Integer, primary_key=True) + + class_name = Column(String(255)) + + resource = Column(String(255)) + hard_limit = Column(Integer) + + +class QuotaUsage(API_BASE): + """Represents the current usage for a given resource.""" + + __tablename__ = 'quota_usages' + __table_args__ = ( + Index('quota_usages_project_id_idx', 'project_id'), + Index('quota_usages_user_id_idx', 'user_id'), + ) + id = Column(Integer, primary_key=True) + + project_id = Column(String(255)) + user_id = Column(String(255)) + resource = Column(String(255), nullable=False) + + in_use = Column(Integer, nullable=False) + reserved = Column(Integer, nullable=False) + + @property + def total(self): + return self.in_use + self.reserved + + until_refresh = Column(Integer) + + +class Reservation(API_BASE): + """Represents a resource reservation for quotas.""" + + __tablename__ = 'reservations' + __table_args__ = ( + Index('reservations_project_id_idx', 'project_id'), + Index('reservations_uuid_idx', 'uuid'), + Index('reservations_expire_idx', 'expire'), + Index('reservations_user_id_idx', 'user_id'), + ) + id = Column(Integer, primary_key=True, nullable=False) + uuid = Column(String(36), nullable=False) + + usage_id = Column(Integer, ForeignKey('quota_usages.id'), nullable=False) + + project_id = Column(String(255)) + user_id = Column(String(255)) + resource = Column(String(255)) + + delta = Column(Integer, nullable=False) + expire = Column(DateTime) + + usage = orm.relationship( + "QuotaUsage", + foreign_keys=usage_id, + primaryjoin='Reservation.usage_id == QuotaUsage.id')