From f482487c8c5aa446c00f5eb001a576c67a026f42 Mon Sep 17 00:00:00 2001 From: Brandon Logan Date: Fri, 22 Aug 2014 11:38:43 -0500 Subject: [PATCH] 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 --- octavia/common/constants.py | 60 +- octavia/common/data_models.py | 193 +++++ octavia/db/api.py | 35 + octavia/db/base_models.py | 70 ++ .../13500e2e978d_update_url_and_name_size.py | 58 ++ ...094013699a_update_load_balancer_amphora.py | 76 ++ octavia/db/models.py | 327 ++++++++ octavia/tests/unit/common/test_constants.py | 2 +- octavia/tests/unit/db/base.py | 67 ++ octavia/tests/unit/db/test_models.py | 763 ++++++++++++++++++ test-requirements.txt | 1 + 11 files changed, 1633 insertions(+), 19 deletions(-) create mode 100644 octavia/common/data_models.py create mode 100644 octavia/db/api.py create mode 100644 octavia/db/base_models.py create mode 100644 octavia/db/migration/alembic_migrations/versions/13500e2e978d_update_url_and_name_size.py create mode 100644 octavia/db/migration/alembic_migrations/versions/4c094013699a_update_load_balancer_amphora.py create mode 100644 octavia/db/models.py create mode 100644 octavia/tests/unit/db/base.py create mode 100644 octavia/tests/unit/db/test_models.py diff --git a/octavia/common/constants.py b/octavia/common/constants.py index 14f7bd5d5b..1385af173f 100644 --- a/octavia/common/constants.py +++ b/octavia/common/constants.py @@ -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) diff --git a/octavia/common/data_models.py b/octavia/common/data_models.py new file mode 100644 index 0000000000..915de4a32e --- /dev/null +++ b/octavia/common/data_models.py @@ -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 \ No newline at end of file diff --git a/octavia/db/api.py b/octavia/db/api.py new file mode 100644 index 0000000000..e466fcff32 --- /dev/null +++ b/octavia/db/api.py @@ -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) diff --git a/octavia/db/base_models.py b/octavia/db/base_models.py new file mode 100644 index 0000000000..26c75f5e0c --- /dev/null +++ b/octavia/db/base_models.py @@ -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) diff --git a/octavia/db/migration/alembic_migrations/versions/13500e2e978d_update_url_and_name_size.py b/octavia/db/migration/alembic_migrations/versions/13500e2e978d_update_url_and_name_size.py new file mode 100644 index 0000000000..35c8de1235 --- /dev/null +++ b/octavia/db/migration/alembic_migrations/versions/13500e2e978d_update_url_and_name_size.py @@ -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)) diff --git a/octavia/db/migration/alembic_migrations/versions/4c094013699a_update_load_balancer_amphora.py b/octavia/db/migration/alembic_migrations/versions/4c094013699a_update_load_balancer_amphora.py new file mode 100644 index 0000000000..b5b65cdb4b --- /dev/null +++ b/octavia/db/migration/alembic_migrations/versions/4c094013699a_update_load_balancer_amphora.py @@ -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'] + ) diff --git a/octavia/db/models.py b/octavia/db/models.py new file mode 100644 index 0000000000..6d98ba4d1f --- /dev/null +++ b/octavia/db/models.py @@ -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")) \ No newline at end of file diff --git a/octavia/tests/unit/common/test_constants.py b/octavia/tests/unit/common/test_constants.py index 59718c6d3e..bc75e44d5e 100644 --- a/octavia/tests/unit/common/test_constants.py +++ b/octavia/tests/unit/common/test_constants.py @@ -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') diff --git a/octavia/tests/unit/db/base.py b/octavia/tests/unit/db/base.py new file mode 100644 index 0000000000..4f67c87665 --- /dev/null +++ b/octavia/tests/unit/db/base.py @@ -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) \ No newline at end of file diff --git a/octavia/tests/unit/db/test_models.py b/octavia/tests/unit/db/test_models.py new file mode 100644 index 0000000000..d720158117 --- /dev/null +++ b/octavia/tests/unit/db/test_models.py @@ -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.FAKE_UUID_2 + new_amphora = self.session.query(models.Amphora).filter_by( + id=amphora.id).first() + self.assertEqual(self.FAKE_UUID_2, new_amphora.amphora_id) + + def test_delete(self): + amphora = self.create_amphora( + self.session) + with self.session.begin(): + self.session.delete(amphora) + self.session.flush() + new_amphora = self.session.query( + models.Amphora).filter_by(id=amphora.id).first() + self.assertIsNone(new_amphora) + + def test_load_balancer_relationship(self): + amphora = self.create_amphora(self.session) + self.associate_amphora(self.load_balancer, amphora) + new_amphora = self.session.query(models.Amphora).filter_by( + id=amphora.id).first() + self.assertIsNotNone(new_amphora.load_balancer) + self.assertIsInstance(new_amphora.load_balancer, models.LoadBalancer) + + +class DataModelConversionTest(base.OctaviaDBTestBase, ModelTestMixin): + + def setUp(self): + super(DataModelConversionTest, self).setUp() + self.pool = self.create_pool(self.session) + self.hm = self.create_health_monitor(self.session, self.pool.id) + self.member = self.create_member(self.session, self.pool.id, + id=self.FAKE_UUID_1, + ip_address='10.0.0.1') + self.sp = self.create_session_persistence(self.session, self.pool.id) + self.lb = self.create_load_balancer(self.session) + self.vip = self.create_vip(self.session, self.lb.id) + self.listener = self.create_listener(self.session, + default_pool_id=self.pool.id, + load_balancer_id=self.lb.id) + self.stats = self.create_listener_statistics(self.session, + self.listener.id) + self.sni = self.create_sni(self.session, listener_id=self.listener.id) + + def test_load_balancer_tree(self): + lb_db = self.session.query(models.LoadBalancer).filter_by( + id=self.lb.id).first() + self.check_load_balancer(lb_db.to_data_model()) + + def test_vip_tree(self): + vip_db = self.session.query(models.Vip).filter_by( + load_balancer_id=self.lb.id).first() + self.check_vip(vip_db.to_data_model()) + + def test_listener_tree(self): + listener_db = self.session.query(models.Listener).filter_by( + id=self.listener.id).first() + self.check_listener(listener_db.to_data_model()) + + def test_sni_tree(self): + sni_db = self.session.query(models.SNI).filter_by( + listener_id=self.listener.id).first() + self.check_sni(sni_db.to_data_model()) + + def test_listener_statistics_tree(self): + stats_db = self.session.query(models.ListenerStatistics).filter_by( + listener_id=self.listener.id).first() + self.check_listener_statistics(stats_db.to_data_model()) + + def test_pool_tree(self): + pool_db = self.session.query(models.Pool).filter_by( + id=self.pool.id).first() + self.check_pool(pool_db.to_data_model()) + + def test_session_persistence_tree(self): + sp_db = self.session.query(models.SessionPersistence).filter_by( + pool_id=self.pool.id).first() + self.check_session_persistence(sp_db.to_data_model()) + + def test_health_monitor_tree(self): + hm_db = self.session.query(models.HealthMonitor).filter_by( + id=self.hm.id).first() + self.check_health_monitor(hm_db.to_data_model()) + + def test_member_tree(self): + member_db = self.session.query(models.Member).filter_by( + id=self.member.id).first() + self.check_member(member_db.to_data_model()) + + def check_load_balancer(self, lb, check_listeners=True, + check_amphorae=True, check_vip=True): + self.assertIsInstance(lb, data_models.LoadBalancer) + self.check_load_balancer_data_model(lb) + self.assertIsInstance(lb.listeners, list) + self.assertIsInstance(lb.amphorae, list) + if check_listeners: + for listener in lb.listeners: + self.check_listener(listener, check_lb=False) + if check_amphorae: + for amphora in lb.amphorae: + self.check_amphora(amphora, check_load_balancer=False) + if check_vip: + self.check_vip(lb.vip, check_lb=False) + + def check_vip(self, vip, check_lb=True): + self.assertIsInstance(vip, data_models.Vip) + self.check_vip_data_model(vip) + if check_lb: + self.check_load_balancer(vip.load_balancer, check_vip=False) + + def check_sni(self, sni, check_listener=True): + self.assertIsInstance(sni, data_models.SNI) + self.check_sni_data_model(sni) + if check_listener: + self.check_listener(sni.listener, check_sni=False) + + def check_listener_statistics(self, stats, check_listener=True): + self.assertIsInstance(stats, data_models.ListenerStatistics) + self.check_listener_statistics_data_model(stats) + if check_listener: + self.check_listener(stats.listener, check_statistics=False) + + def check_amphora(self, amphora, check_load_balancer=True): + self.assertIsInstance(amphora, data_models.Amphora) + self.check_amphora_data_model(amphora) + if check_load_balancer: + self.check_load_balancer(amphora.load_balancer) + + def check_listener(self, listener, check_sni=True, check_pool=True, + check_lb=True, check_statistics=True): + self.assertIsInstance(listener, data_models.Listener) + self.check_listener_data_model(listener) + if check_lb: + self.check_load_balancer(listener.load_balancer) + if check_sni: + c_containers = listener.sni_containers + self.assertIsInstance(c_containers, list) + for sni in c_containers: + self.check_sni(sni, check_listener=False) + if check_pool: + self.check_pool(listener.default_pool, check_listener=False) + if check_statistics: + self.check_listener_statistics(listener.stats, + check_listener=False) + + def check_session_persistence(self, session_persistence, check_pool=True): + self.assertIsInstance(session_persistence, + data_models.SessionPersistence) + self.check_session_persistence_data_model(session_persistence) + if check_pool: + self.check_pool(session_persistence.pool, check_sp=False) + + def check_member(self, member, check_pool=True): + self.assertIsInstance(member, data_models.Member) + self.check_member_data_model(member) + if check_pool: + self.check_pool(member.pool) + + def check_health_monitor(self, health_monitor, check_pool=True): + self.assertIsInstance(health_monitor, data_models.HealthMonitor) + self.check_health_monitor_data_model(health_monitor) + if check_pool: + self.check_pool(health_monitor.pool, check_hm=False) + + def check_pool(self, pool, check_listener=True, check_sp=True, + check_hm=True, check_members=True): + self.assertIsInstance(pool, data_models.Pool) + self.check_pool_data_model(pool) + if check_listener: + self.check_listener(pool.listener, check_pool=False) + if check_sp: + self.check_session_persistence(pool.session_persistence, + check_pool=False) + if check_members: + c_members = pool.members + self.assertIsNotNone(c_members) + self.assertEqual(1, len(c_members)) + for c_member in c_members: + self.check_member(c_member, check_pool=False) + if check_hm: + self.check_health_monitor(pool.health_monitor, check_pool=False) + + def check_load_balancer_data_model(self, lb): + self.assertEqual(self.FAKE_UUID_1, lb.tenant_id) + self.assertEqual(self.FAKE_UUID_1, lb.id) + self.assertEqual(constants.ACTIVE, lb.provisioning_status) + self.assertEqual(True, lb.enabled) + + def check_vip_data_model(self, vip): + self.assertEqual(self.FAKE_UUID_1, vip.load_balancer_id) + + def check_listener_data_model(self, listener): + self.assertEqual(self.FAKE_UUID_1, listener.tenant_id) + self.assertEqual(self.FAKE_UUID_1, listener.id) + self.assertEqual(constants.PROTOCOL_HTTP, listener.protocol) + self.assertEqual(80, listener.protocol_port) + self.assertEqual(constants.ACTIVE, listener.provisioning_status) + self.assertEqual(constants.ONLINE, listener.operating_status) + self.assertEqual(True, listener.enabled) + + def check_sni_data_model(self, sni): + self.assertEqual(self.FAKE_UUID_1, sni.listener_id) + self.assertEqual(self.FAKE_UUID_1, sni.tls_container_id) + + def check_listener_statistics_data_model(self, stats): + self.assertEqual(self.listener.id, stats.listener_id) + self.assertEqual(0, stats.bytes_in) + self.assertEqual(0, stats.bytes_out) + self.assertEqual(0, stats.active_connections) + self.assertEqual(0, stats.total_connections) + + def check_pool_data_model(self, pool): + self.assertEqual(self.FAKE_UUID_1, pool.tenant_id) + self.assertEqual(self.FAKE_UUID_1, pool.id) + self.assertEqual(constants.PROTOCOL_HTTP, pool.protocol) + self.assertEqual(constants.LB_ALGORITHM_ROUND_ROBIN, pool.lb_algorithm) + self.assertEqual(constants.ONLINE, pool.operating_status) + self.assertEqual(True, pool.enabled) + + def check_session_persistence_data_model(self, sp): + self.assertEqual(self.pool.id, sp.pool_id) + self.assertEqual(constants.SESSION_PERSISTENCE_HTTP_COOKIE, sp.type) + + def check_health_monitor_data_model(self, hm): + self.assertEqual(self.FAKE_UUID_1, hm.tenant_id) + self.assertEqual(self.FAKE_UUID_1, hm.id) + self.assertEqual(constants.HEALTH_MONITOR_HTTP, hm.type) + self.assertEqual(1, hm.delay) + self.assertEqual(1, hm.timeout) + self.assertEqual(1, hm.fall_threshold) + self.assertEqual(1, hm.rise_threshold) + self.assertEqual(True, hm.enabled) + + def check_member_data_model(self, member): + self.assertEqual(self.FAKE_UUID_1, member.tenant_id) + self.assertEqual(self.FAKE_UUID_1, member.id) + self.assertEqual(self.pool.id, member.pool_id) + self.assertEqual('10.0.0.1', member.ip_address) + self.assertEqual(80, member.protocol_port) + self.assertEqual(constants.ONLINE, member.operating_status) + self.assertEqual(True, member.enabled) + + def check_amphora_data_model(self, amphora): + self.assertEqual(self.FAKE_UUID_1, amphora.id) + self.assertEqual(self.FAKE_UUID_1, amphora.host_id) + self.assertEqual(constants.ONLINE, amphora.status) + + def check_load_balancer_amphora_data_model(self, amphora): + self.assertEqual(self.FAKE_UUID_1, amphora.amphora_id) + self.assertEqual(self.FAKE_UUID_1, amphora.load_balancer_id) diff --git a/test-requirements.txt b/test-requirements.txt index 3278977b65..677127fcbf 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,6 +7,7 @@ fixtures>=0.3.14 mock>=1.0 python-subunit>=0.0.18 ordereddict +oslotest==1.0.0 testrepository>=0.0.18 testtools>=0.9.34 WebTest>=2.0