Initial creation of db models, modules, and tests

Added sqlalchemy models mirroring initial migration
Added engine and session methods based off oslo.db
Added base db test based off of oslo.db
Added model tests
Added data models
Added data model tests
Added migration for one to many load balancer to amphora
Added migration for name and URL size changes
Added smarter comparison method for data models

Implements: blueprint initial-db-models

Change-Id: I8421093f0952d5e3ef287bfd4979cc5a83af6c09
This commit is contained in:
Brandon Logan
2014-08-22 11:38:43 -05:00
committed by Trevor Vardeman
parent 13b015daea
commit f482487c8c
11 changed files with 1633 additions and 19 deletions

35
octavia/db/api.py Normal file
View File

@@ -0,0 +1,35 @@
# Copyright 2014 Rackspace
#
# 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 oslo.config import cfg
from oslo.db.sqlalchemy import session as db_session
_FACADE = None
def _create_facade_lazily():
global _FACADE
if _FACADE is None:
_FACADE = db_session.EngineFacade.from_config(cfg.CONF)
return _FACADE
def get_engine():
facade = _create_facade_lazily()
return facade.get_engine()
def get_session(**kwargs):
facade = _create_facade_lazily()
return facade.get_session(**kwargs)

70
octavia/db/base_models.py Normal file
View File

@@ -0,0 +1,70 @@
# Copyright 2014 Rackspace
#
# 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 octavia.openstack.common import uuidutils
from oslo.db.sqlalchemy import models
import sqlalchemy as sa
from sqlalchemy.ext import declarative
from sqlalchemy.orm import collections
class OctaviaBase(models.ModelBase):
__data_model__ = None
def to_data_model(self, calling_cls=None):
if not self.__data_model__:
raise NotImplementedError
dm_kwargs = {}
for column in self.__table__.columns:
dm_kwargs[column.name] = getattr(self, column.name)
attr_names = [attr_name for attr_name in dir(self)
if not attr_name.startswith('_')]
for attr_name in attr_names:
attr = getattr(self, attr_name)
if isinstance(attr, OctaviaBase):
if attr.__class__ != calling_cls:
dm_kwargs[attr_name] = attr.to_data_model(
calling_cls=self.__class__)
elif isinstance(attr, collections.InstrumentedList):
dm_kwargs[attr_name] = []
for item in attr:
if isinstance(item, OctaviaBase):
if attr.__class__ != calling_cls:
dm_kwargs[attr_name].append(
item.to_data_model(calling_cls=self.__class__))
else:
dm_kwargs[attr_name].append(item)
return self.__data_model__(**dm_kwargs)
class LookupTableMixin(object):
"""Mixin to add to classes that are lookup tables."""
name = sa.Column(sa.String(255), primary_key=True, nullable=False)
description = sa.Column(sa.String(255), nullable=True)
class IdMixin(object):
"""Id mixin, add to subclasses that have a tenant."""
id = sa.Column(sa.String(36), primary_key=True,
default=uuidutils.generate_uuid)
class TenantMixin(object):
"""Tenant mixin, add to subclasses that have a tenant."""
tenant_id = sa.Column(sa.String(36))
BASE = declarative.declarative_base(cls=OctaviaBase)

View File

@@ -0,0 +1,58 @@
# Copyright 2014 Rackspace
#
# 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.
'''update url and name size
Revision ID: 13500e2e978d
Revises: 4c094013699a
Create Date: 2014-09-18 16:07:04.859812
'''
# revision identifiers, used by Alembic.
revision = '13500e2e978d'
down_revision = '4c094013699a'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.alter_column(u'provisioning_status', u'name',
existing_type=sa.String(255))
op.alter_column(u'operating_status', u'name',
existing_type=sa.String(255))
op.alter_column(u'health_monitor_type', u'name',
existing_type=sa.String(255))
op.alter_column(u'protocol', u'name',
existing_type=sa.String(255))
op.alter_column(u'algorithm', u'name',
existing_type=sa.String(255))
op.alter_column(u'session_persistence_type', u'name',
existing_type=sa.String(255))
def downgrade():
op.alter_column(u'provisioning_status', u'name',
existing_type=sa.String(30))
op.alter_column(u'operating_status', u'name',
existing_type=sa.String(30))
op.alter_column(u'health_monitor_type', u'name',
existing_type=sa.String(30))
op.alter_column(u'protocol', u'name',
existing_type=sa.String(30))
op.alter_column(u'algorithm', u'name',
existing_type=sa.String(30))
op.alter_column(u'session_persistence_type', u'name',
existing_type=sa.String(30))

View File

@@ -0,0 +1,76 @@
# Copyright 2014 Rackspace
#
# 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.
'''update load balancer amphora relationship
Revision ID: 4c094013699a
Revises: 35dee79d5865
Create Date: 2014-09-15 14:42:44.875448
'''
# revision identifiers, used by Alembic.
revision = '4c094013699a'
down_revision = '35dee79d5865'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column(
u'amphora',
sa.Column(u'load_balancer_id', sa.String(36),
sa.ForeignKey(u'load_balancer.id',
name=u'fk_amphora_load_balancer_id'),
nullable=True)
)
op.drop_table(u'load_balancer_amphora')
op.drop_constraint(
u'fk_container_provisioning_status_name', u'amphora',
type_=u'foreignkey'
)
op.create_foreign_key(
u'fk_amphora_provisioning_status_name', u'amphora',
u'provisioning_status', [u'status'], [u'name']
)
def downgrade():
op.drop_constraint(
u'fk_amphora_load_balancer_id', u'amphora', type_=u'foreignkey'
)
op.drop_column(
u'amphora', u'load_balancer_id'
)
op.create_table(
u'load_balancer_amphora',
sa.Column(u'amphora_id', sa.String(36), nullable=False),
sa.Column(u'load_balancer_id', sa.String(36), nullable=False),
sa.ForeignKeyConstraint(
[u'load_balancer_id'], [u'load_balancer.id'],
name=u'fk_load_balancer_amphora_load_balancer_id'),
sa.ForeignKeyConstraint([u'amphora_id'],
[u'amphora.id'],
name=u'fk_load_balancer_amphora_id'),
sa.PrimaryKeyConstraint(u'amphora_id', u'load_balancer_id')
)
op.drop_constraint(
u'fk_amphora_provisioning_status_name', u'amphora',
type_=u'foreignkey'
)
op.create_foreign_key(
u'fk_container_provisioning_status_name', u'amphora',
u'provisioning_status', [u'status'], [u'name']
)

327
octavia/db/models.py Normal file
View File

@@ -0,0 +1,327 @@
# Copyright 2014 Rackspace
#
# 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 sa
from sqlalchemy import orm
from sqlalchemy.orm import validates
from octavia.common import data_models
from octavia.db import base_models
class ProvisioningStatus(base_models.BASE, base_models.LookupTableMixin):
__tablename__ = "provisioning_status"
class OperatingStatus(base_models.BASE, base_models.LookupTableMixin):
__tablename__ = "operating_status"
class Protocol(base_models.BASE, base_models.LookupTableMixin):
__tablename__ = "protocol"
class Algorithm(base_models.BASE, base_models.LookupTableMixin):
__tablename__ = "algorithm"
class SessionPersistenceType(base_models.BASE, base_models.LookupTableMixin):
__tablename__ = "session_persistence_type"
class HealthMonitorType(base_models.BASE, base_models.LookupTableMixin):
__tablename__ = "health_monitor_type"
class SessionPersistence(base_models.BASE):
__data_model__ = data_models.SessionPersistence
__tablename__ = "session_persistence"
pool_id = sa.Column(
sa.String(36),
sa.ForeignKey("pool.id", name="fk_session_persistence_pool_id"),
nullable=False,
primary_key=True)
type = sa.Column(
sa.String(36),
sa.ForeignKey(
"session_persistence_type.name",
name="fk_session_persistence_session_persistence_type_name"),
nullable=False)
cookie_name = sa.Column(sa.String(255), nullable=True)
pool = orm.relationship("Pool", uselist=False,
backref=orm.backref("session_persistence",
uselist=False,
cascade="delete"))
class ListenerStatistics(base_models.BASE):
__data_model__ = data_models.ListenerStatistics
__tablename__ = "listener_statistics"
listener_id = sa.Column(
sa.String(36),
sa.ForeignKey("listener.id",
name="fk_listener_statistics_listener_id"),
primary_key=True,
nullable=False)
bytes_in = sa.Column(sa.BigInteger, nullable=False)
bytes_out = sa.Column(sa.BigInteger, nullable=False)
active_connections = sa.Column(sa.Integer, nullable=False)
total_connections = sa.Column(sa.BigInteger, nullable=False)
listener = orm.relationship("Listener", uselist=False,
backref=orm.backref("stats", uselist=False,
cascade="delete"))
@validates('bytes_in', 'bytes_out',
'active_connections', 'total_connections')
def validate_non_negative_int(self, key, value):
if value < 0:
data = {'key': key, 'value': value}
raise ValueError(data)
# TODO(trevor-vardeman): Repair this functionality after Openstack
# Common is in
# raise ValueError(_('The %(key)s field can not have '
# 'negative value. '
# 'Current value is %(value)d.') % data)
return value
class Member(base_models.BASE, base_models.IdMixin, base_models.TenantMixin):
__data_model__ = data_models.Member
__tablename__ = "member"
__table_args__ = (
sa.UniqueConstraint('pool_id', 'ip_address', 'protocol_port',
name='uq_member_pool_id_ip_address_protocol_port'),
)
pool_id = sa.Column(
sa.String(36),
sa.ForeignKey("pool.id", name="fk_member_pool_id"),
nullable=False)
subnet_id = sa.Column(sa.String(36), nullable=True)
ip_address = sa.Column(sa.String(64), nullable=False)
protocol_port = sa.Column(sa.Integer, nullable=False)
weight = sa.Column(sa.Integer, nullable=True)
operating_status = sa.Column(
sa.String(16),
sa.ForeignKey("operating_status.name",
name="fk_member_operating_status_name"),
nullable=False)
enabled = sa.Column(sa.Boolean(), nullable=False)
pool = orm.relationship("Pool", backref=orm.backref("members",
uselist=True,
cascade="delete"))
class HealthMonitor(base_models.BASE, base_models.IdMixin,
base_models.TenantMixin):
__data_model__ = data_models.HealthMonitor
__tablename__ = "health_monitor"
type = sa.Column(
sa.String(36),
sa.ForeignKey("health_monitor_type.name",
name="fk_health_monitor_health_monitor_type_name"),
nullable=False)
pool_id = sa.Column(
sa.String(36),
sa.ForeignKey("pool.id", name="fk_health_monitor_pool_id"),
nullable=False, primary_key=True)
delay = sa.Column(sa.Integer, nullable=False)
timeout = sa.Column(sa.Integer, nullable=False)
fall_threshold = sa.Column(sa.Integer, nullable=False)
rise_threshold = sa.Column(sa.Integer, nullable=False)
http_method = sa.Column(sa.String(16), nullable=True)
url_path = sa.Column(sa.String(2048), nullable=True)
expected_codes = sa.Column(sa.String(64), nullable=True)
enabled = sa.Column(sa.Boolean, nullable=False)
pool = orm.relationship("Pool", uselist=False,
backref=orm.backref("health_monitor",
uselist=False,
cascade="delete"))
class Pool(base_models.BASE, base_models.IdMixin, base_models.TenantMixin):
__data_model__ = data_models.Pool
__tablename__ = "pool"
name = sa.Column(sa.String(255), nullable=True)
description = sa.Column(sa.String(255), nullable=True)
protocol = sa.Column(
sa.String(16),
sa.ForeignKey("protocol.name", name="fk_pool_protocol_name"),
nullable=False)
lb_algorithm = sa.Column(
sa.String(16),
sa.ForeignKey("algorithm.name", name="fk_pool_algorithm_name"),
nullable=False)
operating_status = sa.Column(
sa.String(16),
sa.ForeignKey("operating_status.name",
name="fk_pool_operating_status_name"),
nullable=False)
enabled = sa.Column(sa.Boolean, nullable=False)
class LoadBalancer(base_models.BASE, base_models.IdMixin,
base_models.TenantMixin):
__data_model__ = data_models.LoadBalancer
__tablename__ = "load_balancer"
name = sa.Column(sa.String(255), nullable=True)
description = sa.Column(sa.String(255), nullable=True)
provisioning_status = sa.Column(
sa.String(16),
sa.ForeignKey("provisioning_status.name",
name="fk_load_balancer_provisioning_status_name"),
nullable=False)
operating_status = sa.Column(
sa.String(16),
sa.ForeignKey("operating_status.name",
name="fk_load_balancer_operating_status_name"),
nullable=False)
enabled = sa.Column(sa.Boolean, nullable=False)
amphorae = orm.relationship("Amphora", uselist=True,
backref=orm.backref("load_balancer",
uselist=False))
class Vip(base_models.BASE):
__data_model__ = data_models.Vip
__tablename__ = "vip"
load_balancer_id = sa.Column(
sa.String(36),
sa.ForeignKey("load_balancer.id",
name="fk_vip_load_balancer_id"),
nullable=False, primary_key=True)
ip_address = sa.Column(sa.String(36), nullable=True)
net_port_id = sa.Column(sa.String(36), nullable=True)
subnet_id = sa.Column(sa.String(36), nullable=True)
floating_ip_id = sa.Column(sa.String(36), nullable=True)
floating_ip_network_id = sa.Column(sa.String(36), nullable=True)
load_balancer = orm.relationship("LoadBalancer", uselist=False,
backref=orm.backref("vip", uselist=False,
cascade="delete"))
class Listener(base_models.BASE, base_models.IdMixin, base_models.TenantMixin):
__data_model__ = data_models.Listener
__tablename__ = "listener"
__table_args__ = (
sa.UniqueConstraint('load_balancer_id', 'protocol_port',
name='uq_listener_load_balancer_id_protocol_port'),
sa.UniqueConstraint('default_pool_id',
name='uq_listener_default_pool_id')
)
name = sa.Column(sa.String(255), nullable=True)
description = sa.Column(sa.String(255), nullable=True)
protocol = sa.Column(
sa.String(16),
sa.ForeignKey("protocol.name", name="fk_listener_protocol_name"),
nullable=False)
protocol_port = sa.Column(sa.Integer(), nullable=False)
connection_limit = sa.Column(sa.Integer, nullable=True)
load_balancer_id = sa.Column(
sa.String(36),
sa.ForeignKey("load_balancer.id", name="fk_listener_load_balancer_id"),
nullable=True)
default_tls_container_id = sa.Column(sa.String(36), nullable=True)
default_pool_id = sa.Column(
sa.String(36),
sa.ForeignKey("pool.id", name="fk_listener_pool_id"),
unique=True, nullable=True)
provisioning_status = sa.Column(
sa.String(16),
sa.ForeignKey("provisioning_status.name",
name="fk_listener_provisioning_status_name"),
nullable=False)
operating_status = sa.Column(
sa.String(16),
sa.ForeignKey("operating_status.name",
name="fk_listener_operating_status_name"),
nullable=False)
enabled = sa.Column(sa.Boolean(), nullable=False)
load_balancer = orm.relationship("LoadBalancer", uselist=False,
backref=orm.backref("listeners",
uselist=True,
cascade="delete"))
default_pool = orm.relationship("Pool", uselist=False,
backref=orm.backref("listener",
uselist=False),
cascade="delete")
class SNI(base_models.BASE):
__data_model__ = data_models.SNI
__tablename__ = "sni"
__table_args__ = (
sa.PrimaryKeyConstraint('listener_id', 'tls_container_id'),
)
listener_id = sa.Column(
sa.String(36),
sa.ForeignKey("listener.id", name="fk_sni_listener_id"),
nullable=False)
tls_container_id = sa.Column(sa.String(36), nullable=False)
position = sa.Column(sa.Integer(), nullable=True)
listener = orm.relationship("Listener", uselist=False,
backref=orm.backref("sni_containers",
uselist=True,
cascade="delete"))
class Amphora(base_models.BASE):
__data_model__ = data_models.Amphora
__tablename__ = "amphora"
id = sa.Column(sa.String(36), nullable=False, primary_key=True,
autoincrement=False)
load_balancer_id = sa.Column(
sa.String(36), sa.ForeignKey("load_balancer.id",
name="fk_amphora_load_balancer_id"),
nullable=True)
host_id = sa.Column(sa.String(36), nullable=False)
status = sa.Column(
sa.String(36),
sa.ForeignKey("provisioning_status.name",
name="fk_container_provisioning_status_name"))