Add Tables for Stateless Architecture
In stateless architecture, we have our own nova and cinder api gateway, which require tables to store resource models. Neutron doesn't need this because we use its original tables. A resource routing table is also added to map resources from top to bottom. Besides, database access functions are moved from "models" module to a new "api" module. Change-Id: Ib4577fa494c232302dbbd6681b7d84b9a9cef00d
This commit is contained in:
parent
16015ac91a
commit
fe14b6f244
|
@ -28,7 +28,7 @@ def main(argv=None, config_files=None):
|
|||
project='tricircle',
|
||||
default_config_files=config_files)
|
||||
migration_helpers.find_migrate_repo()
|
||||
migration_helpers.sync_repo(1)
|
||||
migration_helpers.sync_repo(2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -87,6 +87,8 @@ if [[ "$Q_ENABLE_TRICIRCLE" == "True" ]]; then
|
|||
elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
|
||||
echo_summary "Configuring Tricircle"
|
||||
|
||||
sudo install -d -o $STACK_USER -m 755 $TRICIRCLE_CONF_DIR
|
||||
|
||||
configure_tricircle_api
|
||||
|
||||
echo export PYTHONPATH=\$PYTHONPATH:$TRICIRCLE_DIR >> $RC_DIR/.localrc.auto
|
||||
|
|
|
@ -22,6 +22,7 @@ retrying!=1.3.0,>=1.2.3 # Apache-2.0
|
|||
SQLAlchemy<1.1.0,>=0.9.9
|
||||
WebOb>=1.2.3
|
||||
python-cinderclient>=1.3.1
|
||||
python-glanceclient>=0.18.0
|
||||
python-keystoneclient!=1.8.0,>=1.6.0
|
||||
python-neutronclient>=2.6.0
|
||||
python-novaclient>=2.29.0,!=2.33.0
|
||||
|
|
|
@ -24,7 +24,7 @@ from tricircle.common import client
|
|||
import tricircle.common.context as t_context
|
||||
from tricircle.common import exceptions
|
||||
from tricircle.common import utils
|
||||
from tricircle.db import models
|
||||
import tricircle.db.api as db_api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -126,14 +126,14 @@ class SitesController(rest.RestController):
|
|||
def get_one(self, site_id):
|
||||
context = _extract_context_from_environ(_get_environment())
|
||||
try:
|
||||
return {'site': models.get_site(context, site_id)}
|
||||
return {'site': db_api.get_site(context, site_id)}
|
||||
except exceptions.ResourceNotFound:
|
||||
pecan.abort(404, 'Site with id %s not found' % site_id)
|
||||
|
||||
@expose()
|
||||
def get_all(self):
|
||||
context = _extract_context_from_environ(_get_environment())
|
||||
sites = models.list_sites(context, [])
|
||||
sites = db_api.list_sites(context, [])
|
||||
return {'sites': sites}
|
||||
|
||||
@expose()
|
||||
|
@ -152,7 +152,7 @@ class SitesController(rest.RestController):
|
|||
|
||||
site_filters = [{'key': 'site_name', 'comparator': 'eq',
|
||||
'value': site_name}]
|
||||
sites = models.list_sites(context, site_filters)
|
||||
sites = db_api.list_sites(context, site_filters)
|
||||
if sites:
|
||||
pecan.abort(409, 'Site with name %s exists' % site_name)
|
||||
return
|
||||
|
@ -165,7 +165,7 @@ class SitesController(rest.RestController):
|
|||
site_dict = {'site_id': str(uuid.uuid4()),
|
||||
'site_name': site_name,
|
||||
'az_id': az_name}
|
||||
site = models.create_site(context, site_dict)
|
||||
site = db_api.create_site(context, site_dict)
|
||||
except Exception as e:
|
||||
LOG.debug(e.message)
|
||||
pecan.abort(500, 'Fail to create site')
|
||||
|
@ -182,7 +182,7 @@ class SitesController(rest.RestController):
|
|||
except Exception as e:
|
||||
LOG.debug(e.message)
|
||||
# delete previously created site
|
||||
models.delete_site(context, site['site_id'])
|
||||
db_api.delete_site(context, site['site_id'])
|
||||
pecan.abort(500, 'Fail to create aggregate')
|
||||
return
|
||||
pecan.response.status = 201
|
||||
|
|
|
@ -29,6 +29,7 @@ from oslo_log import log as logging
|
|||
import tricircle.common.context as tricircle_context
|
||||
from tricircle.common import exceptions
|
||||
from tricircle.common import resource_handle
|
||||
from tricircle.db import api
|
||||
from tricircle.db import models
|
||||
|
||||
|
||||
|
@ -165,7 +166,7 @@ class Client(object):
|
|||
return region_service_endpoint_map
|
||||
|
||||
def _get_config_with_retry(self, cxt, filters, site, service, retry):
|
||||
conf_list = models.list_site_service_configurations(cxt, filters)
|
||||
conf_list = api.list_site_service_configurations(cxt, filters)
|
||||
if len(conf_list) > 1:
|
||||
raise exceptions.EndpointNotUnique(site, service)
|
||||
if len(conf_list) == 0:
|
||||
|
@ -182,7 +183,7 @@ class Client(object):
|
|||
site_filters = [{'key': 'site_name',
|
||||
'comparator': 'eq',
|
||||
'value': self.site_name}]
|
||||
site_list = models.list_sites(cxt, site_filters)
|
||||
site_list = api.list_sites(cxt, site_filters)
|
||||
if len(site_list) == 0:
|
||||
raise exceptions.ResourceNotFound(models.Site,
|
||||
self.site_name)
|
||||
|
@ -218,7 +219,7 @@ class Client(object):
|
|||
# use region name to query site
|
||||
site_filters = [{'key': 'site_name', 'comparator': 'eq',
|
||||
'value': region}]
|
||||
site_list = models.list_sites(cxt, site_filters)
|
||||
site_list = api.list_sites(cxt, site_filters)
|
||||
# skip region/site not registered in cascade service
|
||||
if len(site_list) != 1:
|
||||
continue
|
||||
|
@ -228,7 +229,7 @@ class Client(object):
|
|||
'value': site_id},
|
||||
{'key': 'service_type', 'comparator': 'eq',
|
||||
'value': service}]
|
||||
config_list = models.list_site_service_configurations(
|
||||
config_list = api.list_site_service_configurations(
|
||||
cxt, config_filters)
|
||||
|
||||
if len(config_list) > 1:
|
||||
|
@ -237,7 +238,7 @@ class Client(object):
|
|||
config_id = config_list[0]['service_id']
|
||||
update_dict = {
|
||||
'service_url': endpoint_map[region][service]}
|
||||
models.update_site_service_configuration(
|
||||
api.update_site_service_configuration(
|
||||
cxt, config_id, update_dict)
|
||||
else:
|
||||
config_dict = {
|
||||
|
@ -246,7 +247,7 @@ class Client(object):
|
|||
'service_type': service,
|
||||
'service_url': endpoint_map[region][service]
|
||||
}
|
||||
models.create_site_service_configuration(
|
||||
api.create_site_service_configuration(
|
||||
cxt, config_dict)
|
||||
|
||||
def get_endpoint(self, cxt, site_id, service):
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
# 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.
|
||||
|
||||
|
||||
from tricircle.db import core
|
||||
from tricircle.db import models
|
||||
|
||||
|
||||
def create_site(context, site_dict):
|
||||
with context.session.begin():
|
||||
return core.create_resource(context, models.Site, site_dict)
|
||||
|
||||
|
||||
def delete_site(context, site_id):
|
||||
with context.session.begin():
|
||||
return core.delete_resource(context, models.Site, site_id)
|
||||
|
||||
|
||||
def get_site(context, site_id):
|
||||
with context.session.begin():
|
||||
return core.get_resource(context, models.Site, site_id)
|
||||
|
||||
|
||||
def list_sites(context, filters):
|
||||
with context.session.begin():
|
||||
return core.query_resource(context, models.Site, filters)
|
||||
|
||||
|
||||
def update_site(context, site_id, update_dict):
|
||||
with context.session.begin():
|
||||
return core.update_resource(context, models.Site, site_id, update_dict)
|
||||
|
||||
|
||||
def create_site_service_configuration(context, config_dict):
|
||||
with context.session.begin():
|
||||
return core.create_resource(context, models.SiteServiceConfiguration,
|
||||
config_dict)
|
||||
|
||||
|
||||
def delete_site_service_configuration(context, config_id):
|
||||
with context.session.begin():
|
||||
return core.delete_resource(context, models.SiteServiceConfiguration,
|
||||
config_id)
|
||||
|
||||
|
||||
def get_site_service_configuration(context, config_id):
|
||||
with context.session.begin():
|
||||
return core.get_resource(context, models.SiteServiceConfiguration,
|
||||
config_id)
|
||||
|
||||
|
||||
def list_site_service_configurations(context, filters):
|
||||
with context.session.begin():
|
||||
return core.query_resource(context, models.SiteServiceConfiguration,
|
||||
filters)
|
||||
|
||||
|
||||
def update_site_service_configuration(context, config_id, update_dict):
|
||||
with context.session.begin():
|
||||
return core.update_resource(
|
||||
context, models.SiteServiceConfiguration, config_id, update_dict)
|
|
@ -0,0 +1,187 @@
|
|||
# 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 migrate
|
||||
import sqlalchemy as sql
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
|
||||
def MediumText():
|
||||
return sql.Text().with_variant(mysql.MEDIUMTEXT(), 'mysql')
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = sql.MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
aggregates = sql.Table(
|
||||
'aggregates', meta,
|
||||
sql.Column('id', sql.Integer, primary_key=True),
|
||||
sql.Column('name', sql.String(255)),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
aggregate_metadata = sql.Table(
|
||||
'aggregate_metadata', meta,
|
||||
sql.Column('id', sql.Integer, primary_key=True),
|
||||
sql.Column('key', sql.String(255), nullable=False),
|
||||
sql.Column('value', sql.String(255), nullable=False),
|
||||
sql.Column('aggregate_id', sql.Integer, nullable=False),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
instance_types = sql.Table(
|
||||
'instance_types', meta,
|
||||
sql.Column('id', sql.Integer, primary_key=True),
|
||||
sql.Column('name', sql.String(255), unique=True),
|
||||
sql.Column('memory_mb', sql.Integer, nullable=False),
|
||||
sql.Column('vcpus', sql.Integer, nullable=False),
|
||||
sql.Column('root_gb', sql.Integer),
|
||||
sql.Column('ephemeral_gb', sql.Integer),
|
||||
sql.Column('flavorid', sql.String(255), unique=True),
|
||||
sql.Column('swap', sql.Integer, nullable=False, default=0),
|
||||
sql.Column('rxtx_factor', sql.Float, default=1),
|
||||
sql.Column('vcpu_weight', sql.Integer),
|
||||
sql.Column('disabled', sql.Boolean, default=False),
|
||||
sql.Column('is_public', sql.Boolean, default=True),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
instance_type_projects = sql.Table(
|
||||
'instance_type_projects', meta,
|
||||
sql.Column('id', sql.Integer, primary_key=True),
|
||||
sql.Column('instance_type_id', sql.Integer, nullable=False),
|
||||
sql.Column('project_id', sql.String(255)),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
migrate.UniqueConstraint(
|
||||
'instance_type_id', 'project_id',
|
||||
name='uniq_instance_type_projects0instance_type_id0project_id'),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
instance_type_extra_specs = sql.Table(
|
||||
'instance_type_extra_specs', meta,
|
||||
sql.Column('id', sql.Integer, primary_key=True),
|
||||
sql.Column('key', sql.String(255)),
|
||||
sql.Column('value', sql.String(255)),
|
||||
sql.Column('instance_type_id', sql.Integer, nullable=False),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
migrate.UniqueConstraint(
|
||||
'instance_type_id', 'key',
|
||||
name='uniq_instance_type_extra_specs0instance_type_id0key'),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
enum = sql.Enum('ssh', 'x509', metadata=meta, name='keypair_types')
|
||||
enum.create()
|
||||
|
||||
key_pairs = sql.Table(
|
||||
'key_pairs', meta,
|
||||
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
|
||||
sql.Column('name', sql.String(255), nullable=False),
|
||||
sql.Column('user_id', sql.String(255)),
|
||||
sql.Column('fingerprint', sql.String(255)),
|
||||
sql.Column('public_key', MediumText()),
|
||||
sql.Column('type', enum, nullable=False, server_default='ssh'),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
migrate.UniqueConstraint(
|
||||
'user_id', 'name',
|
||||
name='uniq_key_pairs0user_id0name'),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
quotas = sql.Table(
|
||||
'quotas', meta,
|
||||
sql.Column('id', sql.Integer, primary_key=True),
|
||||
sql.Column('project_id', sql.String(255)),
|
||||
sql.Column('resource', sql.String(255), nullable=False),
|
||||
sql.Column('hard_limit', sql.Integer),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
migrate.UniqueConstraint(
|
||||
'project_id', 'resource',
|
||||
name='uniq_quotas0project_id0resource'),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
volume_types = sql.Table(
|
||||
'volume_types', meta,
|
||||
sql.Column('id', sql.String(36), primary_key=True),
|
||||
sql.Column('name', sql.String(255)),
|
||||
sql.Column('description', sql.String(255)),
|
||||
sql.Column('qos_specs_id', sql.String(36)),
|
||||
sql.Column('is_public', sql.Boolean, default=True),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
quality_of_service_specs = sql.Table(
|
||||
'quality_of_service_specs', meta,
|
||||
sql.Column('id', sql.String(36), primary_key=True),
|
||||
sql.Column('specs_id', sql.String(36)),
|
||||
sql.Column('key', sql.String(255)),
|
||||
sql.Column('value', sql.String(255)),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
cascaded_sites_resource_routing = sql.Table(
|
||||
'cascaded_sites_resource_routing', meta,
|
||||
sql.Column('id', sql.Integer, primary_key=True),
|
||||
sql.Column('top_id', sql.String(length=36), nullable=False),
|
||||
sql.Column('bottom_id', sql.String(length=36)),
|
||||
sql.Column('site_id', sql.String(length=64), nullable=False),
|
||||
sql.Column('created_at', sql.DateTime),
|
||||
sql.Column('updated_at', sql.DateTime),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
tables = [aggregates, aggregate_metadata, instance_types,
|
||||
instance_type_projects, instance_type_extra_specs, key_pairs,
|
||||
quotas, volume_types, quality_of_service_specs,
|
||||
cascaded_sites_resource_routing]
|
||||
for table in tables:
|
||||
table.create()
|
||||
|
||||
cascaded_sites = sql.Table('cascaded_sites', meta, autoload=True)
|
||||
|
||||
fkeys = [{'columns': [instance_type_projects.c.instance_type_id],
|
||||
'references': [instance_types.c.id]},
|
||||
{'columns': [instance_type_extra_specs.c.instance_type_id],
|
||||
'references': [instance_types.c.id]},
|
||||
{'columns': [volume_types.c.qos_specs_id],
|
||||
'references': [quality_of_service_specs.c.id]},
|
||||
{'columns': [quality_of_service_specs.c.specs_id],
|
||||
'references': [quality_of_service_specs.c.id]},
|
||||
{'columns': [aggregate_metadata.c.aggregate_id],
|
||||
'references': [aggregates.c.id]},
|
||||
{'columns': [cascaded_sites_resource_routing.c.site_id],
|
||||
'references': [cascaded_sites.c.site_id]}]
|
||||
for fkey in fkeys:
|
||||
migrate.ForeignKeyConstraint(columns=fkey['columns'],
|
||||
refcolumns=fkey['references'],
|
||||
name=fkey.get('name')).create()
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
raise NotImplementedError('downgrade not support')
|
|
@ -14,62 +14,221 @@
|
|||
# under the License.
|
||||
|
||||
|
||||
from oslo_db.sqlalchemy import models
|
||||
import sqlalchemy as sql
|
||||
from sqlalchemy.dialects import mysql
|
||||
from sqlalchemy import schema
|
||||
|
||||
from tricircle.db import core
|
||||
|
||||
|
||||
def create_site(context, site_dict):
|
||||
with context.session.begin():
|
||||
return core.create_resource(context, Site, site_dict)
|
||||
def MediumText():
|
||||
return sql.Text().with_variant(mysql.MEDIUMTEXT(), 'mysql')
|
||||
|
||||
|
||||
def delete_site(context, site_id):
|
||||
with context.session.begin():
|
||||
return core.delete_resource(context, Site, site_id)
|
||||
# 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))
|
||||
|
||||
|
||||
def get_site(context, site_id):
|
||||
with context.session.begin():
|
||||
return core.get_resource(context, Site, site_id)
|
||||
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)
|
||||
|
||||
|
||||
def list_sites(context, filters):
|
||||
with context.session.begin():
|
||||
return core.query_resource(context, Site, filters)
|
||||
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)
|
||||
|
||||
|
||||
def update_site(context, site_id, update_dict):
|
||||
with context.session.begin():
|
||||
return core.update_resource(context, Site, site_id, update_dict)
|
||||
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))
|
||||
|
||||
|
||||
def create_site_service_configuration(context, config_dict):
|
||||
with context.session.begin():
|
||||
return core.create_resource(context, SiteServiceConfiguration,
|
||||
config_dict)
|
||||
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)
|
||||
|
||||
|
||||
def delete_site_service_configuration(context, config_id):
|
||||
with context.session.begin():
|
||||
return core.delete_resource(context,
|
||||
SiteServiceConfiguration, config_id)
|
||||
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')
|
||||
|
||||
|
||||
def list_site_service_configurations(context, filters):
|
||||
with context.session.begin():
|
||||
return core.query_resource(context, SiteServiceConfiguration, filters)
|
||||
class Quota(core.ModelBase, core.DictBase, models.TimestampMixin):
|
||||
"""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'),
|
||||
)
|
||||
attributes = ['id', 'project_id', 'resource', 'hard_limit',
|
||||
'created_at', 'updated_at']
|
||||
|
||||
id = sql.Column(sql.Integer, primary_key=True)
|
||||
project_id = sql.Column(sql.String(255))
|
||||
resource = sql.Column(sql.String(255), nullable=False)
|
||||
hard_limit = sql.Column(sql.Integer)
|
||||
|
||||
|
||||
def update_site_service_configuration(context, config_id, update_dict):
|
||||
with context.session.begin():
|
||||
return core.update_resource(
|
||||
context, SiteServiceConfiguration, config_id, update_dict)
|
||||
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))
|
||||
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))
|
||||
|
||||
|
||||
# Site Model
|
||||
class Site(core.ModelBase, core.DictBase):
|
||||
__tablename__ = 'cascaded_sites'
|
||||
attributes = ['site_id', 'site_name', 'az_id']
|
||||
|
||||
site_id = sql.Column('site_id', sql.String(length=64), primary_key=True)
|
||||
site_name = sql.Column('site_name', sql.String(length=64), unique=True,
|
||||
nullable=False)
|
||||
|
@ -79,6 +238,7 @@ class Site(core.ModelBase, core.DictBase):
|
|||
class SiteServiceConfiguration(core.ModelBase, core.DictBase):
|
||||
__tablename__ = 'cascaded_site_service_configuration'
|
||||
attributes = ['service_id', 'site_id', 'service_type', 'service_url']
|
||||
|
||||
service_id = sql.Column('service_id', sql.String(length=64),
|
||||
primary_key=True)
|
||||
site_id = sql.Column('site_id', sql.String(length=64),
|
||||
|
@ -88,3 +248,22 @@ class SiteServiceConfiguration(core.ModelBase, core.DictBase):
|
|||
nullable=False)
|
||||
service_url = sql.Column('service_url', sql.String(length=512),
|
||||
nullable=False)
|
||||
|
||||
|
||||
# Routing Model
|
||||
class ResourceRouting(core.ModelBase, core.DictBase, models.TimestampMixin):
|
||||
__tablename__ = 'cascaded_sites_resource_routing'
|
||||
__table_args__ = (
|
||||
schema.UniqueConstraint(
|
||||
'top_id', 'site_id',
|
||||
name='cascaded_sites_resource_routing0top_id0site_id'),
|
||||
)
|
||||
attributes = ['id', 'top_id', 'bottom_id', 'site_id',
|
||||
'created_at', 'updated_at']
|
||||
|
||||
id = sql.Column('id', sql.Integer, primary_key=True)
|
||||
top_id = sql.Column('top_id', sql.String(length=36), nullable=False)
|
||||
bottom_id = sql.Column('bottom_id', sql.String(length=36))
|
||||
site_id = sql.Column('site_id', sql.String(length=64),
|
||||
sql.ForeignKey('cascaded_sites.site_id'),
|
||||
nullable=False)
|
||||
|
|
|
@ -22,8 +22,8 @@ import pecan
|
|||
import tricircle.api.controllers.root as root_controller
|
||||
from tricircle.common import client
|
||||
from tricircle.common import context
|
||||
from tricircle.db import api
|
||||
from tricircle.db import core
|
||||
from tricircle.db import models
|
||||
|
||||
|
||||
class ControllerTest(unittest.TestCase):
|
||||
|
@ -52,7 +52,7 @@ class SitesControllerTest(ControllerTest):
|
|||
def test_post_top_site(self):
|
||||
kw = {'name': 'TopSite', 'top': True}
|
||||
site_id = self.controller.post(**kw)['site']['site_id']
|
||||
site = models.get_site(self.context, site_id)
|
||||
site = api.get_site(self.context, site_id)
|
||||
self.assertEqual(site['site_name'], 'TopSite')
|
||||
self.assertEqual(site['az_id'], '')
|
||||
|
||||
|
@ -60,7 +60,7 @@ class SitesControllerTest(ControllerTest):
|
|||
def test_post_bottom_site(self, mock_method):
|
||||
kw = {'name': 'BottomSite'}
|
||||
site_id = self.controller.post(**kw)['site']['site_id']
|
||||
site = models.get_site(self.context, site_id)
|
||||
site = api.get_site(self.context, site_id)
|
||||
self.assertEqual(site['site_name'], 'BottomSite')
|
||||
self.assertEqual(site['az_id'], 'az_BottomSite')
|
||||
mock_method.assert_called_once_with('aggregate', self.context,
|
||||
|
@ -100,7 +100,7 @@ class SitesControllerTest(ControllerTest):
|
|||
'az_Site%d' % i) for i in xrange(2, 4)]
|
||||
mock_method.assert_has_calls(calls)
|
||||
|
||||
@patch.object(models, 'create_site')
|
||||
@patch.object(api, 'create_site')
|
||||
def test_post_create_site_exception(self, mock_method):
|
||||
mock_method.side_effect = Exception
|
||||
kw = {'name': 'BottomSite'}
|
||||
|
@ -118,7 +118,7 @@ class SitesControllerTest(ControllerTest):
|
|||
site_filter = [{'key': 'site_name',
|
||||
'comparator': 'eq',
|
||||
'value': 'BottomSite'}]
|
||||
sites = models.list_sites(self.context, site_filter)
|
||||
sites = api.list_sites(self.context, site_filter)
|
||||
self.assertEqual(len(sites), 0)
|
||||
|
||||
def test_get_one(self):
|
||||
|
|
|
@ -25,8 +25,8 @@ from tricircle.common import client
|
|||
from tricircle.common import context
|
||||
from tricircle.common import exceptions
|
||||
from tricircle.common import resource_handle
|
||||
from tricircle.db import api
|
||||
from tricircle.db import core
|
||||
from tricircle.db import models
|
||||
|
||||
|
||||
FAKE_AZ = 'fake_az'
|
||||
|
@ -138,8 +138,8 @@ class ClientTest(unittest.TestCase):
|
|||
'service_type': FAKE_TYPE,
|
||||
'service_url': FAKE_URL
|
||||
}
|
||||
models.create_site(self.context, site_dict)
|
||||
models.create_site_service_configuration(self.context, config_dict)
|
||||
api.create_site(self.context, site_dict)
|
||||
api.create_site_service_configuration(self.context, config_dict)
|
||||
|
||||
global FAKE_RESOURCES
|
||||
FAKE_RESOURCES = [{'name': 'res1'}, {'name': 'res2'}]
|
||||
|
@ -189,7 +189,7 @@ class ClientTest(unittest.TestCase):
|
|||
cfg.CONF.set_override(name='auto_refresh_endpoint', override=False,
|
||||
group='client')
|
||||
# delete the configuration so endpoint cannot be found
|
||||
models.delete_site_service_configuration(self.context, FAKE_SERVICE_ID)
|
||||
api.delete_site_service_configuration(self.context, FAKE_SERVICE_ID)
|
||||
# auto refresh set to False, directly raise exception
|
||||
self.assertRaises(exceptions.EndpointNotFound,
|
||||
self.client.list_resources,
|
||||
|
@ -211,7 +211,7 @@ class ClientTest(unittest.TestCase):
|
|||
cfg.CONF.set_override(name='auto_refresh_endpoint', override=True,
|
||||
group='client')
|
||||
# delete the configuration so endpoint cannot be found
|
||||
models.delete_site_service_configuration(self.context, FAKE_SERVICE_ID)
|
||||
api.delete_site_service_configuration(self.context, FAKE_SERVICE_ID)
|
||||
|
||||
self.client._get_admin_token = mock.Mock()
|
||||
self.client._get_endpoint_from_keystone = mock.Mock()
|
||||
|
@ -231,7 +231,7 @@ class ClientTest(unittest.TestCase):
|
|||
'service_type': FAKE_TYPE,
|
||||
'service_url': FAKE_URL
|
||||
}
|
||||
models.create_site_service_configuration(self.context, config_dict)
|
||||
api.create_site_service_configuration(self.context, config_dict)
|
||||
self.assertRaises(exceptions.EndpointNotUnique,
|
||||
self.client.list_resources,
|
||||
FAKE_RESOURCE, self.context, [])
|
||||
|
@ -241,9 +241,9 @@ class ClientTest(unittest.TestCase):
|
|||
group='client')
|
||||
update_dict = {'service_url': FAKE_URL_INVALID}
|
||||
# update url to an invalid one
|
||||
models.update_site_service_configuration(self.context,
|
||||
FAKE_SERVICE_ID,
|
||||
update_dict)
|
||||
api.update_site_service_configuration(self.context,
|
||||
FAKE_SERVICE_ID,
|
||||
update_dict)
|
||||
|
||||
# auto refresh set to False, directly raise exception
|
||||
self.assertRaises(exceptions.EndpointNotAvailable,
|
||||
|
@ -255,9 +255,9 @@ class ClientTest(unittest.TestCase):
|
|||
group='client')
|
||||
update_dict = {'service_url': FAKE_URL_INVALID}
|
||||
# update url to an invalid one
|
||||
models.update_site_service_configuration(self.context,
|
||||
FAKE_SERVICE_ID,
|
||||
update_dict)
|
||||
api.update_site_service_configuration(self.context,
|
||||
FAKE_SERVICE_ID,
|
||||
update_dict)
|
||||
|
||||
self.client._get_admin_token = mock.Mock()
|
||||
self.client._get_endpoint_from_keystone = mock.Mock()
|
||||
|
@ -269,8 +269,8 @@ class ClientTest(unittest.TestCase):
|
|||
FAKE_RESOURCE, self.context, [])
|
||||
self.assertEqual(resources, [{'name': 'res1'}, {'name': 'res2'}])
|
||||
|
||||
@patch.object(models, 'create_site_service_configuration')
|
||||
@patch.object(models, 'update_site_service_configuration')
|
||||
@patch.object(api, 'create_site_service_configuration')
|
||||
@patch.object(api, 'update_site_service_configuration')
|
||||
def test_update_endpoint_from_keystone(self, update_mock, create_mock):
|
||||
self.client._get_admin_token = mock.Mock()
|
||||
self.client._get_endpoint_from_keystone = mock.Mock()
|
||||
|
|
|
@ -14,14 +14,95 @@
|
|||
# under the License.
|
||||
|
||||
|
||||
import inspect
|
||||
import unittest
|
||||
|
||||
import oslo_db.exception
|
||||
import sqlalchemy as sql
|
||||
|
||||
from tricircle.common import context
|
||||
from tricircle.common import exceptions
|
||||
from tricircle.db import api
|
||||
from tricircle.db import core
|
||||
from tricircle.db import models
|
||||
|
||||
|
||||
def _get_field_value(column):
|
||||
"""Get field value for resource creating
|
||||
|
||||
returning None indicates that not setting this field in resource dict
|
||||
"""
|
||||
if column.nullable:
|
||||
# just skip nullable column
|
||||
return None
|
||||
if isinstance(column.type, sql.Text):
|
||||
return 'fake_text'
|
||||
elif isinstance(column.type, sql.Enum):
|
||||
return column.type.enums[0]
|
||||
elif isinstance(column.type, sql.String):
|
||||
return 'fake_str'
|
||||
elif isinstance(column.type, sql.Integer):
|
||||
return 1
|
||||
elif isinstance(column.type, sql.Float):
|
||||
return 1.0
|
||||
elif isinstance(column.type, sql.Boolean):
|
||||
return True
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _construct_resource_dict(resource_class):
|
||||
ret_dict = {}
|
||||
for field in inspect.getmembers(resource_class):
|
||||
if field[0] in resource_class.attributes:
|
||||
field_value = _get_field_value(field[1])
|
||||
if field_value is not None:
|
||||
ret_dict[field[0]] = field_value
|
||||
return ret_dict
|
||||
|
||||
|
||||
def _sort_model_by_foreign_key(resource_class_list):
|
||||
"""Apply topology sorting to obey foreign key constraints"""
|
||||
relation_map = {}
|
||||
table_map = {}
|
||||
# {table: (set(depend_on_table), set(depended_by_table))}
|
||||
for resource_class in resource_class_list:
|
||||
table = resource_class.__tablename__
|
||||
if table not in relation_map:
|
||||
relation_map[table] = (set(), set())
|
||||
if table not in table_map:
|
||||
table_map[table] = resource_class
|
||||
for field in inspect.getmembers(resource_class):
|
||||
if field[0] in resource_class.attributes:
|
||||
f_keys = field[1].foreign_keys
|
||||
for f_key in f_keys:
|
||||
f_table = f_key.column.table.name
|
||||
# just skip self reference
|
||||
if table == f_table:
|
||||
continue
|
||||
relation_map[table][0].add(f_table)
|
||||
if f_table not in relation_map:
|
||||
relation_map[f_table] = (set(), set())
|
||||
relation_map[f_table][1].add(table)
|
||||
|
||||
sorted_list = []
|
||||
total = len(relation_map)
|
||||
|
||||
while len(sorted_list) < total:
|
||||
candidate_table = None
|
||||
for table in relation_map:
|
||||
# no depend-on table
|
||||
if not relation_map[table][0]:
|
||||
candidate_table = table
|
||||
sorted_list.append(candidate_table)
|
||||
for _table in relation_map[table][1]:
|
||||
relation_map[_table][0].remove(table)
|
||||
break
|
||||
del relation_map[candidate_table]
|
||||
|
||||
return [table_map[table] for table in sorted_list]
|
||||
|
||||
|
||||
class ModelsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
core.initialize()
|
||||
|
@ -40,7 +121,7 @@ class ModelsTest(unittest.TestCase):
|
|||
site = {'site_id': 'test_site_uuid',
|
||||
'site_name': 'test_site',
|
||||
'az_id': 'test_az_uuid'}
|
||||
site_ret = models.create_site(self.context, site)
|
||||
site_ret = api.create_site(self.context, site)
|
||||
self.assertEqual(site_ret, site)
|
||||
|
||||
configuration = {
|
||||
|
@ -49,19 +130,19 @@ class ModelsTest(unittest.TestCase):
|
|||
'service_type': 'nova',
|
||||
'service_url': 'http://test_url'
|
||||
}
|
||||
config_ret = models.create_site_service_configuration(self.context,
|
||||
configuration)
|
||||
config_ret = api.create_site_service_configuration(self.context,
|
||||
configuration)
|
||||
self.assertEqual(config_ret, configuration)
|
||||
|
||||
def test_update(self):
|
||||
site = {'site_id': 'test_site_uuid',
|
||||
'site_name': 'test_site',
|
||||
'az_id': 'test_az1_uuid'}
|
||||
models.create_site(self.context, site)
|
||||
api.create_site(self.context, site)
|
||||
update_dict = {'site_id': 'fake_uuid',
|
||||
'site_name': 'test_site2',
|
||||
'az_id': 'test_az2_uuid'}
|
||||
ret = models.update_site(self.context, 'test_site_uuid', update_dict)
|
||||
ret = api.update_site(self.context, 'test_site_uuid', update_dict)
|
||||
# primary key value will not be updated
|
||||
self.assertEqual(ret['site_id'], 'test_site_uuid')
|
||||
self.assertEqual(ret['site_name'], 'test_site2')
|
||||
|
@ -71,9 +152,9 @@ class ModelsTest(unittest.TestCase):
|
|||
site = {'site_id': 'test_site_uuid',
|
||||
'site_name': 'test_site',
|
||||
'az_id': 'test_az_uuid'}
|
||||
models.create_site(self.context, site)
|
||||
models.delete_site(self.context, 'test_site_uuid')
|
||||
self.assertRaises(exceptions.ResourceNotFound, models.get_site,
|
||||
api.create_site(self.context, site)
|
||||
api.delete_site(self.context, 'test_site_uuid')
|
||||
self.assertRaises(exceptions.ResourceNotFound, api.get_site,
|
||||
self.context, 'test_site_uuid')
|
||||
|
||||
def test_query(self):
|
||||
|
@ -83,19 +164,48 @@ class ModelsTest(unittest.TestCase):
|
|||
site2 = {'site_id': 'test_site2_uuid',
|
||||
'site_name': 'test_site2',
|
||||
'az_id': 'test_az2_uuid'}
|
||||
models.create_site(self.context, site1)
|
||||
models.create_site(self.context, site2)
|
||||
api.create_site(self.context, site1)
|
||||
api.create_site(self.context, site2)
|
||||
filters = [{'key': 'site_name',
|
||||
'comparator': 'eq',
|
||||
'value': 'test_site2'}]
|
||||
sites = models.list_sites(self.context, filters)
|
||||
sites = api.list_sites(self.context, filters)
|
||||
self.assertEqual(len(sites), 1)
|
||||
self.assertEqual(sites[0], site2)
|
||||
filters = [{'key': 'site_name',
|
||||
'comparator': 'eq',
|
||||
'value': 'test_site3'}]
|
||||
sites = models.list_sites(self.context, filters)
|
||||
sites = api.list_sites(self.context, filters)
|
||||
self.assertEqual(len(sites), 0)
|
||||
|
||||
def test_resources(self):
|
||||
"""Create all the resources to test model definition"""
|
||||
try:
|
||||
model_list = []
|
||||
for _, model_class in inspect.getmembers(models):
|
||||
if inspect.isclass(model_class) and (
|
||||
issubclass(model_class, core.ModelBase)):
|
||||
model_list.append(model_class)
|
||||
for model_class in _sort_model_by_foreign_key(model_list):
|
||||
create_dict = _construct_resource_dict(model_class)
|
||||
with self.context.session.begin():
|
||||
core.create_resource(
|
||||
self.context, model_class, create_dict)
|
||||
except Exception:
|
||||
self.fail('test_resources raised Exception unexpectedly')
|
||||
|
||||
def test_resource_routing_unique_key(self):
|
||||
site = {'site_id': 'test_site1_uuid',
|
||||
'site_name': 'test_site1',
|
||||
'az_id': 'test_az1_uuid'}
|
||||
api.create_site(self.context, site)
|
||||
routing = {'top_id': 'top_uuid',
|
||||
'site_id': 'test_site1_uuid'}
|
||||
with self.context.session.begin():
|
||||
core.create_resource(self.context, models.ResourceRouting, routing)
|
||||
self.assertRaises(oslo_db.exception.DBDuplicateEntry,
|
||||
core.create_resource,
|
||||
self.context, models.ResourceRouting, routing)
|
||||
|
||||
def tearDown(self):
|
||||
core.ModelBase.metadata.drop_all(core.get_engine())
|
||||
|
|
Loading…
Reference in New Issue