2bc2db5ae6
Implement asynchronous job management to ensure jobs can be successfully completed even if those jobs temporally fail for some reasons. The detailed design can be found in section 9 in design document. This patch focuses on defining database schema and building lock mechanism to avoid running the same type of jobs at the same time. Enabling workers to rerun failed job and purge old job records will be covered in the following patches. Change-Id: I87d0056a95eb7cb963e1c3599062a60299472298
408 lines
16 KiB
Python
408 lines
16 KiB
Python
# Copyright 2015 Huawei Technologies Co., Ltd.
|
|
# 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 sqlalchemy as sql
|
|
from sqlalchemy.dialects import mysql
|
|
from sqlalchemy.orm import relationship
|
|
from sqlalchemy import schema
|
|
|
|
from oslo_db.sqlalchemy import models
|
|
from oslo_utils import timeutils
|
|
|
|
from tricircle.db import core
|
|
|
|
|
|
def MediumText():
|
|
return sql.Text().with_variant(mysql.MEDIUMTEXT(), 'mysql')
|
|
|
|
|
|
# Resource Model
|
|
class Aggregate(core.ModelBase, core.DictBase, models.TimestampMixin):
|
|
"""Represents a cluster of hosts that exists in this zone."""
|
|
__tablename__ = 'aggregates'
|
|
attributes = ['id', 'name', 'created_at', 'updated_at']
|
|
|
|
id = sql.Column(sql.Integer, primary_key=True)
|
|
name = sql.Column(sql.String(255), unique=True)
|
|
|
|
|
|
class AggregateMetadata(core.ModelBase, core.DictBase, models.TimestampMixin):
|
|
"""Represents a metadata key/value pair for an aggregate."""
|
|
__tablename__ = 'aggregate_metadata'
|
|
__table_args__ = (
|
|
sql.Index('aggregate_metadata_key_idx', 'key'),
|
|
schema.UniqueConstraint(
|
|
'aggregate_id', 'key',
|
|
name='uniq_aggregate_metadata0aggregate_id0key'),
|
|
)
|
|
attributes = ['id', 'key', 'value', 'aggregate_id',
|
|
'created_at', 'updated_at']
|
|
|
|
id = sql.Column(sql.Integer, primary_key=True)
|
|
key = sql.Column(sql.String(255), nullable=False)
|
|
value = sql.Column(sql.String(255), nullable=False)
|
|
aggregate_id = sql.Column(sql.Integer,
|
|
sql.ForeignKey('aggregates.id'), nullable=False)
|
|
|
|
|
|
class InstanceTypes(core.ModelBase, core.DictBase, models.TimestampMixin):
|
|
"""Represents possible flavors for instances.
|
|
|
|
Note: instance_type and flavor are synonyms and the term instance_type is
|
|
deprecated and in the process of being removed.
|
|
"""
|
|
__tablename__ = 'instance_types'
|
|
attributes = ['id', 'name', 'memory_mb', 'vcpus', 'root_gb',
|
|
'ephemeral_gb', 'flavorid', 'swap', 'rxtx_factor',
|
|
'vcpu_weight', 'disabled', 'is_public', 'created_at',
|
|
'updated_at']
|
|
|
|
# Internal only primary key/id
|
|
id = sql.Column(sql.Integer, primary_key=True)
|
|
name = sql.Column(sql.String(255), unique=True)
|
|
memory_mb = sql.Column(sql.Integer, nullable=False)
|
|
vcpus = sql.Column(sql.Integer, nullable=False)
|
|
root_gb = sql.Column(sql.Integer)
|
|
ephemeral_gb = sql.Column(sql.Integer)
|
|
# Public facing id will be renamed public_id
|
|
flavorid = sql.Column(sql.String(255), unique=True)
|
|
swap = sql.Column(sql.Integer, nullable=False, default=0)
|
|
rxtx_factor = sql.Column(sql.Float, default=1)
|
|
vcpu_weight = sql.Column(sql.Integer)
|
|
disabled = sql.Column(sql.Boolean, default=False)
|
|
is_public = sql.Column(sql.Boolean, default=True)
|
|
|
|
|
|
class InstanceTypeProjects(core.ModelBase, core.DictBase,
|
|
models.TimestampMixin):
|
|
"""Represent projects associated instance_types."""
|
|
__tablename__ = 'instance_type_projects'
|
|
__table_args__ = (schema.UniqueConstraint(
|
|
'instance_type_id', 'project_id',
|
|
name='uniq_instance_type_projects0instance_type_id0project_id'),
|
|
)
|
|
attributes = ['id', 'instance_type_id', 'project_id', 'created_at',
|
|
'updated_at']
|
|
|
|
id = sql.Column(sql.Integer, primary_key=True)
|
|
instance_type_id = sql.Column(sql.Integer,
|
|
sql.ForeignKey('instance_types.id'),
|
|
nullable=False)
|
|
project_id = sql.Column(sql.String(255))
|
|
|
|
|
|
class InstanceTypeExtraSpecs(core.ModelBase, core.DictBase,
|
|
models.TimestampMixin):
|
|
"""Represents additional specs as key/value pairs for an instance_type."""
|
|
__tablename__ = 'instance_type_extra_specs'
|
|
__table_args__ = (
|
|
sql.Index('instance_type_extra_specs_instance_type_id_key_idx',
|
|
'instance_type_id', 'key'),
|
|
schema.UniqueConstraint(
|
|
'instance_type_id', 'key',
|
|
name='uniq_instance_type_extra_specs0instance_type_id0key'),
|
|
{'mysql_collate': 'utf8_bin'},
|
|
)
|
|
attributes = ['id', 'key', 'value', 'instance_type_id', 'created_at',
|
|
'updated_at']
|
|
|
|
id = sql.Column(sql.Integer, primary_key=True)
|
|
key = sql.Column(sql.String(255))
|
|
value = sql.Column(sql.String(255))
|
|
instance_type_id = sql.Column(sql.Integer,
|
|
sql.ForeignKey('instance_types.id'),
|
|
nullable=False)
|
|
|
|
|
|
class KeyPair(core.ModelBase, core.DictBase, models.TimestampMixin):
|
|
"""Represents a public key pair for ssh / WinRM."""
|
|
__tablename__ = 'key_pairs'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint('user_id', 'name',
|
|
name='uniq_key_pairs0user_id0name'),
|
|
)
|
|
attributes = ['id', 'name', 'user_id', 'fingerprint', 'public_key', 'type',
|
|
'created_at', 'updated_at']
|
|
|
|
id = sql.Column(sql.Integer, primary_key=True, nullable=False)
|
|
name = sql.Column(sql.String(255), nullable=False)
|
|
user_id = sql.Column(sql.String(255))
|
|
fingerprint = sql.Column(sql.String(255))
|
|
public_key = sql.Column(MediumText())
|
|
type = sql.Column(sql.Enum('ssh', 'x509', name='keypair_types'),
|
|
nullable=False, server_default='ssh')
|
|
|
|
|
|
# Quota part are ported from Cinder for hierarchy multi-tenancy quota control
|
|
class QuotasBase(models.ModelBase, core.DictBase,
|
|
models.TimestampMixin, models.SoftDeleteMixin):
|
|
"""QuotasBase.
|
|
|
|
provide base class for quota series tables. For it inherits from
|
|
models.ModelBase, this is different from other tables
|
|
"""
|
|
__table_args__ = {'mysql_engine': 'InnoDB'}
|
|
|
|
metadata = None
|
|
|
|
def delete(self, session):
|
|
"""Delete this object."""
|
|
self.deleted = True
|
|
self.deleted_at = timeutils.utcnow()
|
|
self.save(session=session)
|
|
|
|
|
|
class Quotas(core.ModelBase, QuotasBase):
|
|
"""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__ = ()
|
|
attributes = ['id', 'project_id', 'resource',
|
|
'hard_limit', 'allocated',
|
|
'created_at', 'updated_at', 'deleted_at', 'deleted']
|
|
|
|
id = sql.Column(sql.Integer, primary_key=True)
|
|
project_id = sql.Column(sql.String(255), index=True)
|
|
resource = sql.Column(sql.String(255), nullable=False)
|
|
hard_limit = sql.Column(sql.Integer)
|
|
allocated = sql.Column(sql.Integer, default=0)
|
|
|
|
|
|
class QuotaClasses(core.ModelBase, QuotasBase):
|
|
"""Quota_classes.
|
|
|
|
make default quota as a update-able quota.Mainly for the command
|
|
quota-class-show and quota-class-update
|
|
"""
|
|
__tablename__ = 'quota_classes'
|
|
__table_args__ = ()
|
|
attributes = ['id', 'class_name', 'resource', 'hard_limit',
|
|
'created_at', 'updated_at', 'deleted_at', 'deleted']
|
|
|
|
id = sql.Column(sql.Integer, primary_key=True)
|
|
class_name = sql.Column(sql.String(255), index=True)
|
|
resource = sql.Column(sql.String(255), nullable=False)
|
|
hard_limit = sql.Column(sql.Integer)
|
|
|
|
|
|
class QuotaUsages(core.ModelBase, QuotasBase):
|
|
"""Quota_uages.
|
|
|
|
store quota usages for project resource
|
|
"""
|
|
__tablename__ = 'quota_usages'
|
|
__table_args__ = ()
|
|
attributes = ['id', 'project_id', 'user_id', 'resource',
|
|
'in_use', 'reserved', 'until_refresh',
|
|
'created_at', 'updated_at', 'deleted_at', 'deleted']
|
|
|
|
id = sql.Column(sql.Integer, primary_key=True)
|
|
project_id = sql.Column(sql.String(255), index=True)
|
|
user_id = sql.Column(sql.String(255), index=True)
|
|
resource = sql.Column(sql.String(255), nullable=False)
|
|
|
|
in_use = sql.Column(sql.Integer)
|
|
reserved = sql.Column(sql.Integer, default=0)
|
|
|
|
until_refresh = sql.Column(sql.Integer, default=0)
|
|
|
|
@property
|
|
def total(self):
|
|
return self.in_use + self.reserved
|
|
|
|
|
|
class Reservation(core.ModelBase, QuotasBase):
|
|
"""Reservation.
|
|
|
|
Represents a resource reservation for quotas
|
|
"""
|
|
__tablename__ = 'reservations'
|
|
__table_args__ = ()
|
|
attributes = ['id', 'uuid', 'usage_id', 'project_id', 'resource',
|
|
'delta', 'expire',
|
|
'created_at', 'updated_at', 'deleted_at', 'deleted']
|
|
|
|
id = sql.Column(sql.Integer, primary_key=True)
|
|
uuid = sql.Column(sql.String(36), nullable=False)
|
|
|
|
usage_id = sql.Column(sql.Integer,
|
|
sql.ForeignKey('quota_usages.id'),
|
|
nullable=False)
|
|
|
|
project_id = sql.Column(sql.String(255), index=True)
|
|
resource = sql.Column(sql.String(255))
|
|
|
|
delta = sql.Column(sql.Integer)
|
|
expire = sql.Column(sql.DateTime, nullable=False)
|
|
|
|
usage = relationship(
|
|
"QuotaUsages",
|
|
foreign_keys=usage_id,
|
|
primaryjoin='and_(Reservation.usage_id == QuotaUsages.id,'
|
|
'QuotaUsages.deleted == 0)')
|
|
|
|
|
|
class VolumeTypes(core.ModelBase, core.DictBase, models.TimestampMixin):
|
|
"""Represent possible volume_types of volumes offered."""
|
|
__tablename__ = "volume_types"
|
|
attributes = ['id', 'name', 'description', 'qos_specs_id', 'is_public',
|
|
'created_at', 'updated_at']
|
|
|
|
id = sql.Column(sql.String(36), primary_key=True)
|
|
name = sql.Column(sql.String(255), unique=True)
|
|
description = sql.Column(sql.String(255))
|
|
# A reference to qos_specs entity
|
|
qos_specs_id = sql.Column(sql.String(36),
|
|
sql.ForeignKey('quality_of_service_specs.id'))
|
|
is_public = sql.Column(sql.Boolean, default=True)
|
|
|
|
|
|
class QualityOfServiceSpecs(core.ModelBase, core.DictBase,
|
|
models.TimestampMixin):
|
|
"""Represents QoS specs as key/value pairs.
|
|
|
|
QoS specs is standalone entity that can be associated/disassociated
|
|
with volume types (one to many relation). Adjacency list relationship
|
|
pattern is used in this model in order to represent following hierarchical
|
|
data with in flat table, e.g, following structure
|
|
|
|
qos-specs-1 'Rate-Limit'
|
|
|
|
|
+------> consumer = 'front-end'
|
|
+------> total_bytes_sec = 1048576
|
|
+------> total_iops_sec = 500
|
|
|
|
qos-specs-2 'QoS_Level1'
|
|
|
|
|
+------> consumer = 'back-end'
|
|
+------> max-iops = 1000
|
|
+------> min-iops = 200
|
|
|
|
is represented by:
|
|
|
|
id specs_id key value
|
|
------ -------- ------------- -----
|
|
UUID-1 NULL QoSSpec_Name Rate-Limit
|
|
UUID-2 UUID-1 consumer front-end
|
|
UUID-3 UUID-1 total_bytes_sec 1048576
|
|
UUID-4 UUID-1 total_iops_sec 500
|
|
UUID-5 NULL QoSSpec_Name QoS_Level1
|
|
UUID-6 UUID-5 consumer back-end
|
|
UUID-7 UUID-5 max-iops 1000
|
|
UUID-8 UUID-5 min-iops 200
|
|
"""
|
|
__tablename__ = 'quality_of_service_specs'
|
|
attributes = ['id', 'specs_id', 'key', 'value', 'created_at', 'updated_at']
|
|
|
|
id = sql.Column(sql.String(36), primary_key=True)
|
|
specs_id = sql.Column(sql.String(36), sql.ForeignKey(id))
|
|
key = sql.Column(sql.String(255))
|
|
value = sql.Column(sql.String(255))
|
|
|
|
|
|
# Pod Model
|
|
class Pod(core.ModelBase, core.DictBase):
|
|
__tablename__ = 'cascaded_pods'
|
|
attributes = ['pod_id', 'pod_name', 'pod_az_name', 'dc_name', 'az_name']
|
|
|
|
pod_id = sql.Column('pod_id', sql.String(length=36), primary_key=True)
|
|
pod_name = sql.Column('pod_name', sql.String(length=255), unique=True,
|
|
nullable=False)
|
|
pod_az_name = sql.Column('pod_az_name', sql.String(length=255),
|
|
nullable=True)
|
|
dc_name = sql.Column('dc_name', sql.String(length=255), nullable=True)
|
|
az_name = sql.Column('az_name', sql.String(length=255), nullable=False)
|
|
|
|
|
|
class PodServiceConfiguration(core.ModelBase, core.DictBase):
|
|
__tablename__ = 'cascaded_pod_service_configuration'
|
|
attributes = ['service_id', 'pod_id', 'service_type', 'service_url']
|
|
|
|
service_id = sql.Column('service_id', sql.String(length=64),
|
|
primary_key=True)
|
|
pod_id = sql.Column('pod_id', sql.String(length=64),
|
|
sql.ForeignKey('cascaded_pods.pod_id'),
|
|
nullable=False)
|
|
service_type = sql.Column('service_type', sql.String(length=64),
|
|
nullable=False)
|
|
service_url = sql.Column('service_url', sql.String(length=512),
|
|
nullable=False)
|
|
|
|
|
|
# Tenant and pod binding model
|
|
class PodBinding(core.ModelBase, core.DictBase, models.TimestampMixin):
|
|
__tablename__ = 'pod_binding'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint(
|
|
'tenant_id', 'pod_id',
|
|
name='pod_binding0tenant_id0pod_id'),
|
|
)
|
|
attributes = ['id', 'tenant_id', 'pod_id',
|
|
'created_at', 'updated_at']
|
|
|
|
id = sql.Column(sql.String(36), primary_key=True)
|
|
tenant_id = sql.Column('tenant_id', sql.String(36), nullable=False)
|
|
pod_id = sql.Column('pod_id', sql.String(36),
|
|
sql.ForeignKey('cascaded_pods.pod_id'),
|
|
nullable=False)
|
|
|
|
|
|
# Routing Model
|
|
class ResourceRouting(core.ModelBase, core.DictBase, models.TimestampMixin):
|
|
__tablename__ = 'cascaded_pods_resource_routing'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint(
|
|
'top_id', 'pod_id',
|
|
name='cascaded_pods_resource_routing0top_id0pod_id'),
|
|
)
|
|
attributes = ['id', 'top_id', 'bottom_id', 'pod_id', 'project_id',
|
|
'resource_type', 'created_at', 'updated_at']
|
|
|
|
id = sql.Column('id', sql.Integer, primary_key=True)
|
|
top_id = sql.Column('top_id', sql.String(length=127), nullable=False)
|
|
bottom_id = sql.Column('bottom_id', sql.String(length=36))
|
|
pod_id = sql.Column('pod_id', sql.String(length=64),
|
|
sql.ForeignKey('cascaded_pods.pod_id'),
|
|
nullable=False)
|
|
project_id = sql.Column('project_id', sql.String(length=36))
|
|
resource_type = sql.Column('resource_type', sql.String(length=64),
|
|
nullable=False)
|
|
|
|
|
|
class Job(core.ModelBase, core.DictBase):
|
|
__tablename__ = 'job'
|
|
__table_args__ = (
|
|
schema.UniqueConstraint(
|
|
'type', 'status', 'resource_id', 'extra_id',
|
|
name='job0type0status0resource_id0extra_id'),
|
|
)
|
|
|
|
attributes = ['id', 'type', 'timestamp', 'status', 'resource_id',
|
|
'extra_id']
|
|
|
|
id = sql.Column('id', sql.String(length=36), primary_key=True)
|
|
type = sql.Column('type', sql.String(length=36))
|
|
timestamp = sql.Column('timestamp', sql.TIMESTAMP,
|
|
server_default=sql.text('CURRENT_TIMESTAMP'))
|
|
status = sql.Column('status', sql.String(length=36))
|
|
resource_id = sql.Column('resource_id', sql.String(length=36))
|
|
extra_id = sql.Column('extra_id', sql.String(length=36))
|