trio2o/tricircle/db/models.py
zhiyuan_cai 2bc2db5ae6 Asynchronous job management(part 1)
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
2016-03-31 10:08:38 +08:00

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))