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
changes/18/116718/11
Brandon Logan 9 years ago committed by Trevor Vardeman
parent 13b015daea
commit f482487c8c

@ -1,28 +1,52 @@
# Copyright (c) 2012-2014 OpenStack Foundation.
# 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
# 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
# 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.
# 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.
LB_METHOD_ROUND_ROBIN = 'ROUND_ROBIN'
LB_METHOD_LEAST_CONNECTIONS = 'LEAST_CONNECTIONS'
LB_METHOD_SOURCE_IP = 'SOURCE_IP'
LB_ALGORITHM_ROUND_ROBIN = 'ROUND_ROBIN'
LB_ALGORITHM_LEAST_CONNECTIONS = 'LEAST_CONNECTIONS'
LB_ALGORITHM_SOURCE_IP = 'SOURCE_IP'
SUPPORTED_LB_ALGORITHMS = (LB_ALGORITHM_LEAST_CONNECTIONS,
LB_ALGORITHM_ROUND_ROBIN,
LB_ALGORITHM_SOURCE_IP)
PROTOCOL_TCP = 'TCP'
PROTOCOL_HTTP = 'HTTP'
PROTOCOL_HTTPS = 'HTTPS'
PROTOCOL_UDP = 'UDP'
SESSION_PERSISTENCE_SOURCE_IP = 'SOURCE_IP'
SESSION_PERSISTENCE_HTTP_COOKIE = 'HTTP_COOKIE'
SUPPORTED_SP_TYPES = (SESSION_PERSISTENCE_SOURCE_IP,
SESSION_PERSISTENCE_HTTP_COOKIE)
HEALTH_MONITOR_PING = 'PING'
HEALTH_MONITOR_TCP = 'TCP'
HEALTH_MONITOR_HTTP = 'HTTP'
HEALTH_MONITOR_HTTPS = 'HTTPS'
SUPPORTED_HEALTH_MONITOR_TYPES = (HEALTH_MONITOR_HTTP, HEALTH_MONITOR_HTTPS,
HEALTH_MONITOR_PING, HEALTH_MONITOR_TCP)
PROTOCOL_TCP = 'TCP'
PROTOCOL_HTTP = 'HTTP'
PROTOCOL_HTTPS = 'HTTPS'
SUPPORTED_PROTOCOLS = (PROTOCOL_TCP, PROTOCOL_HTTPS, PROTOCOL_HTTP)
ACTIVE = 'ACTIVE'
PENDING_DELETE = 'PENDING_DELETE'
PENDING_UPDATE = 'PENDING_UPDATE'
PENDING_CREATE = 'PENDING_CREATE'
DELETED = 'DELETED'
ERROR = 'ERROR'
SUPPORTED_PROVISIONING_STATUSES = (ACTIVE, PENDING_DELETE, PENDING_CREATE,
PENDING_UPDATE, DELETED, ERROR)
ONLINE = 'ONLINE'
OFFLINE = 'OFFLINE'
DEGRADED = 'DEGRADED'
ERROR = 'ERROR'
SUPPORTED_OPERATING_STATUSES = (ONLINE, OFFLINE, DEGRADED, ERROR)

@ -0,0 +1,193 @@
# Copyright (c) 2014 Rackspace
# 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.
class BaseDataModel(object):
# NOTE(brandon-logan) This does not discover dicts for relationship
# attributes.
def to_dict(self):
ret = {}
for attr in self.__dict__:
if (attr.startswith('_') or
isinstance(getattr(self, attr), BaseDataModel)):
continue
ret[attr] = self.__dict__[attr]
return ret
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.to_dict() == other.to_dict()
return False
class SessionPersistence(BaseDataModel):
def __init__(self, pool_id=None, type=None, cookie_name=None,
pool=None):
self.pool_id = pool_id
self.type = type
self.cookie_name = cookie_name
self.pool = pool
class ListenerStatistics(BaseDataModel):
def __init__(self, listener_id=None, bytes_in=None, bytes_out=None,
active_connections=None, total_connections=None,
listener=None):
self.listener_id = listener_id
self.bytes_in = bytes_in
self.bytes_out = bytes_out
self.active_connections = active_connections
self.total_connections = total_connections
self.listener = listener
class HealthMonitor(BaseDataModel):
def __init__(self, id=None, tenant_id=None, pool_id=None, type=None,
delay=None, timeout=None, fall_threshold=None,
rise_threshold=None, http_method=None, url_path=None,
expected_codes=None, enabled=None, pool=None):
self.id = id
self.tenant_id = tenant_id
self.pool_id = pool_id
self.type = type
self.delay = delay
self.timeout = timeout
self.fall_threshold = fall_threshold
self.rise_threshold = rise_threshold
self.http_method = http_method
self.url_path = url_path
self.expected_codes = expected_codes
self.enabled = enabled
self.pool = pool
class Pool(BaseDataModel):
def __init__(self, id=None, tenant_id=None, name=None, description=None,
protocol=None, lb_algorithm=None, enabled=None,
operating_status=None, members=None, health_monitor=None,
session_persistence=None, listener=None):
self.id = id
self.tenant_id = tenant_id
self.name = name
self.description = description
self.protocol = protocol
self.lb_algorithm = lb_algorithm
self.enabled = enabled
self.operating_status = operating_status
self.members = members or []
self.health_monitor = health_monitor
self.session_persistence = session_persistence
self.listener = listener
class Member(BaseDataModel):
def __init__(self, id=None, tenant_id=None, pool_id=None, ip_address=None,
protocol_port=None, weight=None, enabled=None,
subnet_id=None, operating_status=None, pool=None):
self.id = id
self.tenant_id = tenant_id
self.pool_id = pool_id
self.ip_address = ip_address
self.protocol_port = protocol_port
self.weight = weight
self.enabled = enabled
self.subnet_id = subnet_id
self.operating_status = operating_status
self.pool = pool
class Listener(BaseDataModel):
def __init__(self, id=None, tenant_id=None, name=None, description=None,
default_pool_id=None, load_balancer_id=None, protocol=None,
protocol_port=None, connection_limit=None,
enabled=None, provisioning_status=None, operating_status=None,
default_tls_container_id=None, stats=None, default_pool=None,
load_balancer=None, sni_containers=None):
self.id = id
self.tenant_id = tenant_id
self.name = name
self.description = description
self.default_pool_id = default_pool_id
self.load_balancer_id = load_balancer_id
self.protocol = protocol
self.protocol_port = protocol_port
self.connection_limit = connection_limit
self.enabled = enabled
self.provisioning_status = provisioning_status
self.operating_status = operating_status
self.default_tls_container_id = default_tls_container_id
self.stats = stats
self.default_pool = default_pool
self.load_balancer = load_balancer
self.sni_containers = sni_containers
class LoadBalancer(BaseDataModel):
def __init__(self, id=None, tenant_id=None, name=None, description=None,
provisioning_status=None, operating_status=None, enabled=None,
vip=None, listeners=None, amphorae=None):
self.id = id
self.tenant_id = tenant_id
self.name = name
self.description = description
self.provisioning_status = provisioning_status
self.operating_status = operating_status
self.enabled = enabled
self.vip = vip
self.listeners = listeners or []
self.amphorae = amphorae or []
class Vip(BaseDataModel):
def __init__(self, load_balancer_id=None, ip_address=None,
net_port_id=None, subnet_id=None, floating_ip_id=None,
floating_ip_network_id=None, load_balancer=None):
self.load_balancer_id = load_balancer_id
self.ip_address = ip_address
self.net_port_id = net_port_id
self.subnet_id = subnet_id
self.floating_ip_id = floating_ip_id
self.floating_ip_network_id = floating_ip_network_id
self.load_balancer = load_balancer
class SNI(BaseDataModel):
def __init__(self, listener_id=None, position=None, listener=None,
tls_container_id=None):
self.listener_id = listener_id
self.position = position
self.listener = listener
self.tls_container_id = tls_container_id
class Amphora(BaseDataModel):
def __init__(self, id=None, load_balancer_id=None, host_id=None,
status=None, load_balancer=None):
self.id = id
self.load_balancer_id = load_balancer_id
self.host_id = host_id
self.status = status
self.load_balancer = load_balancer

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

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

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

@ -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']
)

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

@ -20,4 +20,4 @@ class TestConstants(base.TestCase):
# Rough sanity test of module import; not meant to be exhaustive
def test_import(self):
self.assertEqual(constants.PROTOCOL_UDP, 'UDP')
self.assertEqual(constants.PROTOCOL_TCP, 'TCP')

@ -0,0 +1,67 @@
# 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.db.sqlalchemy import test_base
from octavia.common import constants
from octavia.db import base_models
from octavia.db import models
class OctaviaDBTestBase(test_base.DbTestCase):
def setUp(self):
super(OctaviaDBTestBase, self).setUp()
# needed for closure
engine = self.engine
base_models.BASE.metadata.create_all(bind=engine)
self._seed_lookup_tables()
def unregister_models():
"""Unregister all data models."""
base_models.BASE.metadata.drop_all(bind=engine)
self.addCleanup(unregister_models)
self.session = self._get_session()
def _get_session(self):
return self.sessionmaker(bind=self.engine, expire_on_commit=True)
def _seed_lookup_tables(self):
session = self._get_session()
self._seed_lookup_table(
session, constants.SUPPORTED_PROVISIONING_STATUSES,
models.ProvisioningStatus)
self._seed_lookup_table(
session, constants.SUPPORTED_HEALTH_MONITOR_TYPES,
models.HealthMonitorType)
self._seed_lookup_table(
session, constants.SUPPORTED_LB_ALGORITHMS,
models.Algorithm)
self._seed_lookup_table(
session, constants.SUPPORTED_PROTOCOLS,
models.Protocol)
self._seed_lookup_table(
session, constants.SUPPORTED_OPERATING_STATUSES,
models.OperatingStatus)
self._seed_lookup_table(
session, constants.SUPPORTED_SP_TYPES,
models.SessionPersistenceType)
def _seed_lookup_table(self, session, name_list, model_cls):
for name in name_list:
with session.begin():
model = model_cls(name=name)
session.add(model)

@ -0,0 +1,763 @@
# 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.common import constants
from octavia.common import data_models
from octavia.db import models
from octavia.tests.unit.db import base
class ModelTestMixin(object):
FAKE_UUID_1 = '0123456789012345678901234567890123456'
FAKE_UUID_2 = '1234567890123456789012345678901234567'
def _insert(self, session, model_cls, model_kwargs):
with session.begin():
model = model_cls(**model_kwargs)
session.add(model)
return model
def associate_amphora(self, load_balancer, amphora):
load_balancer.amphorae.append(amphora)
def create_listener(self, session, **overrides):
kwargs = {'tenant_id': self.FAKE_UUID_1,
'id': self.FAKE_UUID_1,
'protocol': constants.PROTOCOL_HTTP,
'protocol_port': 80,
'provisioning_status': constants.ACTIVE,
'operating_status': constants.ONLINE,
'enabled': True}
kwargs.update(overrides)
return self._insert(session, models.Listener, kwargs)
def create_listener_statistics(self, session, listener_id, **overrides):
kwargs = {'listener_id': listener_id,
'bytes_in': 0,
'bytes_out': 0,
'active_connections': 0,
'total_connections': 0}
kwargs.update(overrides)
return self._insert(session, models.ListenerStatistics, kwargs)
def create_pool(self, session, **overrides):
kwargs = {'tenant_id': self.FAKE_UUID_1,
'id': self.FAKE_UUID_1,
'protocol': constants.PROTOCOL_HTTP,
'lb_algorithm': constants.LB_ALGORITHM_ROUND_ROBIN,
'operating_status': constants.ONLINE,
'enabled': True}
kwargs.update(overrides)
return self._insert(session, models.Pool, kwargs)
def create_session_persistence(self, session, pool_id, **overrides):
kwargs = {'pool_id': pool_id,
'type': constants.SESSION_PERSISTENCE_HTTP_COOKIE}
kwargs.update(overrides)
return self._insert(session, models.SessionPersistence, kwargs)
def create_health_monitor(self, session, pool_id, **overrides):
kwargs = {'tenant_id': self.FAKE_UUID_1,
'id': self.FAKE_UUID_1,
'pool_id': pool_id,
'type': constants.HEALTH_MONITOR_HTTP,
'delay': 1,
'timeout': 1,
'fall_threshold': 1,
'rise_threshold': 1,
'enabled': True}
kwargs.update(overrides)
return self._insert(session, models.HealthMonitor, kwargs)
def create_member(self, session, pool_id, **overrides):
kwargs = {'tenant_id': self.FAKE_UUID_1,
'id': self.FAKE_UUID_1,
'pool_id': pool_id,
'ip_address': '10.0.0.1',
'protocol_port': 80,
'operating_status': constants.ONLINE,
'enabled': True}
kwargs.update(overrides)
return self._insert(session, models.Member, kwargs)
def create_load_balancer(self, session, **overrides):
kwargs = {'tenant_id': self.FAKE_UUID_1,
'id': self.FAKE_UUID_1,
'provisioning_status': constants.ACTIVE,
'operating_status': constants.ONLINE,
'enabled': True}
kwargs.update(overrides)
return self._insert(session, models.LoadBalancer, kwargs)
def create_vip(self, session, load_balancer_id, **overrides):
kwargs = {'load_balancer_id': load_balancer_id}
kwargs.update(overrides)
return self._insert(session, models.Vip, kwargs)
def create_sni(self, session, **overrides):
kwargs = {'listener_id': self.FAKE_UUID_1,
'tls_container_id': self.FAKE_UUID_1}
kwargs.update(overrides)
return self._insert(session, models.SNI, kwargs)
def create_amphora(self, session, **overrides):
kwargs = {'id': self.FAKE_UUID_1,
'host_id': self.FAKE_UUID_1,
'status': constants.ONLINE}
kwargs.update(overrides)
return self._insert(session, models.Amphora, kwargs)
class PoolModelTest(base.OctaviaDBTestBase, ModelTestMixin):
def test_create(self):
self.create_pool(self.session)
def test_update(self):
pool = self.create_pool(self.session)
id = pool.id
pool.name = 'test1'
new_pool = self.session.query(
models.Pool).filter_by(id=id).first()
self.assertEqual('test1', new_pool.name)
def test_delete(self):
pool = self.create_pool(self.session)
id = pool.id
with self.session.begin():
self.session.delete(pool)
self.session.flush()
new_pool = self.session.query(
models.Pool).filter_by(id=id).first()
self.assertIsNone(new_pool)
def test_member_relationship(self):
pool = self.create_pool(self.session)
self.create_member(self.session, pool.id, id=self.FAKE_UUID_1,
ip_address="10.0.0.1")
self.create_member(self.session, pool.id, id=self.FAKE_UUID_2,
ip_address="10.0.0.2")
new_pool = self.session.query(
models.Pool).filter_by(id=pool.id).first()
self.assertIsNotNone(new_pool.members)
self.assertEqual(2, len(new_pool.members))
self.assertTrue(isinstance(new_pool.members[0], models.Member))
def test_health_monitor_relationship(self):
pool = self.create_pool(self.session)
self.create_health_monitor(self.session, pool.id)
new_pool = self.session.query(models.Pool).filter_by(
id=pool.id).first()
self.assertIsNotNone(new_pool.health_monitor)
self.assertTrue(isinstance(new_pool.health_monitor,
models.HealthMonitor))
def test_session_persistence_relationship(self):
pool = self.create_pool(self.session)
self.create_session_persistence(self.session, pool_id=pool.id)
new_pool = self.session.query(models.Pool).filter_by(
id=pool.id).first()
self.assertIsNotNone(new_pool.session_persistence)
self.assertTrue(isinstance(new_pool.session_persistence,
models.SessionPersistence))
def test_listener_relationship(self):
pool = self.create_pool(self.session)
self.create_listener(self.session, default_pool_id=pool.id)
new_pool = self.session.query(models.Pool).filter_by(
id=pool.id).first()
self.assertIsNotNone(new_pool.listener)
self.assertTrue(isinstance(new_pool.listener, models.Listener))
class MemberModelTest(base.OctaviaDBTestBase, ModelTestMixin):
def setUp(self):
super(MemberModelTest, self).setUp()
self.pool = self.create_pool(self.session)
def test_create(self):
self.create_member(self.session, self.pool.id)
def test_update(self):
member = self.create_member(self.session, self.pool.id)
member_id = member.id
member.name = 'test1'
new_member = self.session.query(
models.Member).filter_by(id=member_id).first()
self.assertEqual('test1', new_member.name)
def test_delete(self):
member = self.create_member(self.session, self.pool.id)
member_id = member.id
with self.session.begin():
self.session.delete(member)
self.session.flush()
new_member = self.session.query(
models.Member).filter_by(id=member_id).first()
self.assertIsNone(new_member)
def test_pool_relationship(self):
member = self.create_member(self.session, self.pool.id,
id=self.FAKE_UUID_1,
ip_address="10.0.0.1")
self.create_member(self.session, self.pool.id, id=self.FAKE_UUID_2,
ip_address="10.0.0.2")
new_member = self.session.query(models.Member).filter_by(
id=member.id).first()
self.assertIsNotNone(new_member.pool)
self.assertTrue(isinstance(new_member.pool, models.Pool))
class SessionPersistenceModelTest(base.OctaviaDBTestBase, ModelTestMixin):
def setUp(self):
super(SessionPersistenceModelTest, self).setUp()
self.pool = self.create_pool(self.session)
def test_create(self):
self.create_session_persistence(self.session, self.pool.id)
def test_update(self):
session_persistence = self.create_session_persistence(self.session,
self.pool.id)
session_persistence.name = 'test1'
new_session_persistence = self.session.query(
models.SessionPersistence).filter_by(pool_id=self.pool.id).first()
self.assertEqual('test1', new_session_persistence.name)
def test_delete(self):
session_persistence = self.create_session_persistence(self.session,
self.pool.id)
with self.session.begin():
self.session.delete(session_persistence)
self.session.flush()
new_session_persistence = self.session.query(
models.SessionPersistence).filter_by(pool_id=self.pool.id).first()
self.assertIsNone(new_session_persistence)
def test_pool_relationship(self):
self.create_session_persistence(self.session, self.pool.id)
new_persistence = self.session.query(
models.SessionPersistence).filter_by(pool_id=self.pool.id).first()
self.assertIsNotNone(new_persistence.pool)
self.assertTrue(isinstance(new_persistence.pool, models.Pool))
class ListenerModelTest(base.OctaviaDBTestBase, ModelTestMixin):
def test_create(self):
self.create_listener(self.session)
def test_update(self):
listener = self.create_listener(self.session)
listener_id = listener.id
listener.name = 'test1'
new_listener = self.session.query(
models.Listener).filter_by(id=listener_id).first()
self.assertEqual('test1', new_listener.name)
def test_delete(self):
listener = self.create_listener(self.session)
listener_id = listener.id
with self.session.begin():
self.session.delete(listener)
self.session.flush()
new_listener = self.session.query(
models.Listener).filter_by(id=listener_id).first()
self.assertIsNone(new_listener)
def test_load_balancer_relationship(self):
lb = self.create_load_balancer(self.session)
listener = self.create_listener(self.session, load_balancer_id=lb.id)
new_listener = self.session.query(
models.Listener).filter_by(id=listener.id).first()
self.assertIsNotNone(new_listener.load_balancer)
self.assertTrue(isinstance(new_listener.load_balancer,
models.LoadBalancer))
def test_listener_statistics_relationship(self):
listener = self.create_listener(self.session)
self.create_listener_statistics(self.session, listener_id=listener.id)
new_listener = self.session.query(models.Listener).filter_by(
id=listener.id).first()
self.assertIsNotNone(new_listener.stats)
self.assertTrue(isinstance(new_listener.stats,
models.ListenerStatistics))
def test_pool_relationship(self):
pool = self.create_pool(self.session)
listener = self.create_listener(self.session, default_pool_id=pool.id)
new_listener = self.session.query(models.Listener).filter_by(
id=listener.id).first()
self.assertIsNotNone(new_listener.default_pool)
self.assertTrue(isinstance(new_listener.default_pool, models.Pool))
def test_sni_relationship(self):
listener = self.create_listener(self.session)
self.create_sni(self.session, listener_id=listener.id,
tls_container_id=self.FAKE_UUID_1)
self.create_sni(self.session, listener_id=listener.id,
tls_container_id=self.FAKE_UUID_2)
new_listener = self.session.query(models.Listener).filter_by(
id=listener.id).first()
self.assertIsNotNone(new_listener.sni_containers)
self.assertEqual(2, len(new_listener.sni_containers))
class ListenerStatisticsModelTest(base.OctaviaDBTestBase, ModelTestMixin):
def setUp(self):
super(ListenerStatisticsModelTest, self).setUp()
self.listener = self.create_listener(self.session)
def test_create(self):
self.create_listener_statistics(self.session, self.listener.id)
def test_update(self):
stats = self.create_listener_statistics(self.session, self.listener.id)
stats.name = 'test1'
new_stats = self.session.query(models.ListenerStatistics).filter_by(
listener_id=self.listener.id).first()
self.assertEqual('test1', new_stats.name)
def test_delete(self):
stats = self.create_listener_statistics(self.session, self.listener.id)
with self.session.begin():
self.session.delete(stats)
self.session.flush()
new_stats = self.session.query(models.ListenerStatistics).filter_by(
listener_id=self.listener.id).first()
self.assertIsNone(new_stats)
def test_listener_relationship(self):
self.create_listener_statistics(self.session, self.listener.id)
new_stats = self.session.query(models.ListenerStatistics).filter_by(
listener_id=self.listener.id).first()
self.assertIsNotNone(new_stats.listener)
self.assertTrue(isinstance(new_stats.listener, models.Listener))
class HealthMonitorModelTest(base.OctaviaDBTestBase, ModelTestMixin):
def setUp(self):
super(HealthMonitorModelTest, self).setUp()
self.pool = self.create_pool(self.session)
def test_create(self):
self.create_health_monitor(self.session, self.pool.id)
def test_update(self):
health_monitor = self.create_health_monitor(self.session, self.pool.id)
health_monitor_id = health_monitor.id
health_monitor.name = 'test1'
new_health_monitor = self.session.query(
models.HealthMonitor).filter_by(id=health_monitor_id).first()
self.assertEqual('test1', new_health_monitor.name)
def test_delete(self):
health_monitor = self.create_health_monitor(self.session, self.pool.id)
health_monitor_id = health_monitor.id
with self.session.begin():
self.session.delete(health_monitor)
self.session.flush()
new_health_monitor = self.session.query(
models.HealthMonitor).filter_by(id=health_monitor_id).first()
self.assertIsNone(new_health_monitor)
def test_pool_relationship(self):
health_monitor = self.create_health_monitor(self.session, self.pool.id)
new_health_monitor = self.session.query(
models.HealthMonitor).filter_by(id=health_monitor.id).first()
self.assertIsNotNone(new_health_monitor.pool)
self.assertTrue(isinstance(new_health_monitor.pool, models.Pool))
class LoadBalancerModelTest(base.OctaviaDBTestBase, ModelTestMixin):
def test_create(self):
self.create_load_balancer(self.session)
def test_update(self):
load_balancer = self.create_load_balancer(self.session)
lb_id = load_balancer.id
load_balancer.name = 'test1'
new_load_balancer = self.session.query(
models.LoadBalancer).filter_by(id=lb_id).first()
self.assertEqual('test1', new_load_balancer.name)
def test_delete(self):
load_balancer = self.create_load_balancer(self.session)
lb_id = load_balancer.id
with self.session.begin():
self.session.delete(load_balancer)
self.session.flush()
new_load_balancer = self.session.query(
models.LoadBalancer).filter_by(id=lb_id).first()
self.assertIsNone(new_load_balancer)
def test_listener_relationship(self):
load_balancer = self.create_load_balancer(self.session)
self.create_listener(self.session, load_balancer_id=load_balancer.id)
new_load_balancer = self.session.query(
models.LoadBalancer).filter_by(id=load_balancer.id).first()
self.assertIsNotNone(new_load_balancer.listeners)
self.assertEqual(1, len(new_load_balancer.listeners))
def test_load_balancer_amphora_relationship(self):
load_balancer = self.create_load_balancer(self.session)
amphora = self.create_amphora(self.session)
self.associate_amphora(load_balancer, amphora)
new_load_balancer = self.session.query(
models.LoadBalancer).filter_by(id=load_balancer.id).first()
self.assertIsNotNone(new_load_balancer.amphorae)
self.assertEqual(1, len(new_load_balancer.amphorae))
def test_load_balancer_vip_relationship(self):
load_balancer = self.create_load_balancer(self.session)
self.create_vip(self.session, load_balancer.id)
new_load_balancer = self.session.query(
models.LoadBalancer).filter_by(id=load_balancer.id).first()
self.assertIsNotNone(new_load_balancer.vip)
self.assertIsInstance(new_load_balancer.vip, models.Vip)
class VipModelTest(base.OctaviaDBTestBase, ModelTestMixin):
def setUp(self):
super(VipModelTest, self).setUp()
self.load_balancer = self.create_load_balancer(self.session)
def test_create(self):
self.create_vip(self.session, self.load_balancer.id)
def test_update(self):
vip = self.create_vip(self.session, self.load_balancer.id)
vip.ip_address = "10.0.0.1"
new_vip = self.session.query(models.Vip).filter_by(
load_balancer_id=self.load_balancer.id).first()
self.assertEqual("10.0.0.1", new_vip.ip_address)
def test_delete(self):
vip = self.create_vip(self.session, self.load_balancer.id)
with self.session.begin():
self.session.delete(vip)
self.session.flush()
new_vip = self.session.query(models.Vip).filter_by(
load_balancer_id=vip.load_balancer_id).first()
self.assertIsNone(new_vip)
def test_vip_load_balancer_relationship(self):
self.create_vip(self.session, self.load_balancer.id)
new_vip = self.session.query(models.Vip).filter_by(
load_balancer_id=self.load_balancer.id).first()
self.assertIsNotNone(new_vip.load_balancer)
self.assertTrue(isinstance(new_vip.load_balancer, models.LoadBalancer))
class SNIModelTest(base.OctaviaDBTestBase, ModelTestMixin):
def setUp(self):
super(SNIModelTest, self).setUp()
self.listener = self.create_listener(self.session)
def test_create(self):
self.create_sni(self.session, listener_id=self.listener.id)
def test_update(self):
sni = self.create_sni(self.session, listener_id=self.listener.id)
sni.listener_id = self.FAKE_UUID_2
new_sni = self.session.query(
models.SNI).filter_by(listener_id=self.FAKE_UUID_2).first()
self.assertEqual(self.FAKE_UUID_2, new_sni.listener_id)
def test_delete(self):
sni = self.create_sni(self.session, listener_id=self.listener.id)
with self.session.begin():
self.session.delete(sni)
self.session.flush()
new_sni = self.session.query(
models.SNI).filter_by(listener_id=self.listener.id).first()
self.assertIsNone(new_sni)
def test_sni_relationship(self):
self.create_sni(self.session, listener_id=self.listener.id)
new_sni = self.session.query(models.SNI).filter_by(
listener_id=self.listener.id).first()
self.assertIsNotNone(new_sni.listener)
self.assertTrue(isinstance(new_sni.listener, models.Listener))
class AmphoraModelTest(base.OctaviaDBTestBase, ModelTestMixin):
def setUp(self):
super(AmphoraModelTest, self).setUp()
self.load_balancer = self.create_load_balancer(self.session)
def test_create(self):
self.create_amphora(self.session)
def test_update(self):
amphora = self.create_amphora(
self.session)
amphora.amphora_id = self.