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