# Copyright 2019 VMware, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from neutron.db.models import servicetype as st_db from neutron.db import models_v2 from neutron_lib.db import constants as db_const from neutron_lib.db import model_base import sqlalchemy as sa from sqlalchemy.ext import orderinglist from sqlalchemy import orm from vmware_nsx.tests.unit.services.lbaas import lb_constants as lb_const class SessionPersistenceV2(model_base.BASEV2): NAME = 'session_persistence' __tablename__ = "lbaas_sessionpersistences" pool_id = sa.Column(sa.String(36), sa.ForeignKey("lbaas_pools.id"), primary_key=True, nullable=False) type = sa.Column(sa.Enum(*lb_const.SUPPORTED_SP_TYPES, name="lbaas_sesssionpersistences_typev2"), nullable=False) cookie_name = sa.Column(sa.String(1024), nullable=True) class LoadBalancerStatistics(model_base.BASEV2): """Represents load balancer statistics.""" NAME = 'loadbalancer_stats' __tablename__ = "lbaas_loadbalancer_statistics" loadbalancer_id = sa.Column(sa.String(36), sa.ForeignKey("lbaas_loadbalancers.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.BigInteger, nullable=False) total_connections = sa.Column(sa.BigInteger, nullable=False) @orm.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('The %(key)s field can not have ' 'negative value. ' 'Current value is %(value)d.' % data) return value class MemberV2(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents a v2 neutron load balancer member.""" NAME = 'member' __tablename__ = "lbaas_members" __table_args__ = ( sa.schema.UniqueConstraint('pool_id', 'address', 'protocol_port', name='uniq_pool_address_port_v2'), ) pool_id = sa.Column(sa.String(36), sa.ForeignKey("lbaas_pools.id"), nullable=False) address = sa.Column(sa.String(64), nullable=False) protocol_port = sa.Column(sa.Integer, nullable=False) weight = sa.Column(sa.Integer, nullable=True) admin_state_up = sa.Column(sa.Boolean(), nullable=False) subnet_id = sa.Column(sa.String(36), nullable=True) provisioning_status = sa.Column(sa.String(16), nullable=False) operating_status = sa.Column(sa.String(16), nullable=False) name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE), nullable=True) @property def root_loadbalancer(self): return self.pool.loadbalancer @property def to_api_dict(self): def to_dict(sa_model, attributes): ret = {} for attr in attributes: value = getattr(sa_model, attr) ret[attr] = value return ret ret_dict = to_dict(self, [ 'id', 'tenant_id', 'pool_id', 'address', 'protocol_port', 'weight', 'admin_state_up', 'subnet_id', 'name']) return ret_dict class HealthMonitorV2(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents a v2 neutron load balancer healthmonitor.""" NAME = 'healthmonitor' __tablename__ = "lbaas_healthmonitors" type = sa.Column(sa.Enum(*lb_const.SUPPORTED_HEALTH_MONITOR_TYPES, name="healthmonitors_typev2"), nullable=False) delay = sa.Column(sa.Integer, nullable=False) timeout = sa.Column(sa.Integer, nullable=False) max_retries = sa.Column(sa.Integer, nullable=False) http_method = sa.Column(sa.String(16), nullable=True) url_path = sa.Column(sa.String(255), nullable=True) expected_codes = sa.Column(sa.String(64), nullable=True) provisioning_status = sa.Column(sa.String(16), nullable=False) admin_state_up = sa.Column(sa.Boolean(), nullable=False) name = sa.Column(sa.String(db_const.NAME_FIELD_SIZE), nullable=True) max_retries_down = sa.Column(sa.Integer, nullable=True) @property def root_loadbalancer(self): return self.pool.loadbalancer @property def to_api_dict(self): def to_dict(sa_model, attributes): ret = {} for attr in attributes: value = getattr(sa_model, attr) ret[attr] = value return ret ret_dict = to_dict(self, [ 'id', 'tenant_id', 'type', 'delay', 'timeout', 'max_retries', 'http_method', 'url_path', 'expected_codes', 'admin_state_up', 'name', 'max_retries_down']) ret_dict['pools'] = [] if self.pool: ret_dict['pools'].append({'id': self.pool.id}) if self.type in [lb_const.HEALTH_MONITOR_TCP, lb_const.HEALTH_MONITOR_PING]: ret_dict.pop('http_method') ret_dict.pop('url_path') ret_dict.pop('expected_codes') return ret_dict class LoadBalancer(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents a v2 neutron load balancer.""" NAME = 'loadbalancer' __tablename__ = "lbaas_loadbalancers" name = sa.Column(sa.String(255)) description = sa.Column(sa.String(255)) vip_subnet_id = sa.Column(sa.String(36), nullable=False) vip_port_id = sa.Column(sa.String(36), sa.ForeignKey( 'ports.id', name='fk_lbaas_loadbalancers_ports_id')) vip_address = sa.Column(sa.String(36)) provisioning_status = sa.Column(sa.String(16), nullable=False) operating_status = sa.Column(sa.String(16), nullable=False) admin_state_up = sa.Column(sa.Boolean(), nullable=False) vip_port = orm.relationship(models_v2.Port) stats = orm.relationship( LoadBalancerStatistics, uselist=False, backref=orm.backref("loadbalancer", uselist=False), cascade="all, delete-orphan") provider = orm.relationship( st_db.ProviderResourceAssociation, uselist=False, primaryjoin="LoadBalancer.id==ProviderResourceAssociation.resource_id", foreign_keys=[st_db.ProviderResourceAssociation.resource_id], # NOTE(ihrachys) it's not exactly clear why we would need to have the # backref created (and not e.g. just back_populates= link) since we # don't use the reverse property anywhere, but it helps with # accommodating to the new neutron code that automatically detects # obsolete foreign key state and expires affected relationships. The # code is located in neutron/db/api.py and assumes all relationships # should have backrefs. backref='loadbalancer', # this is only for old API backwards compatibility because when a load # balancer is deleted the pool ID should be the same as the load # balancer ID and should not be cleared out in this table viewonly=True) flavor_id = sa.Column(sa.String(36), sa.ForeignKey( 'flavors.id', name='fk_lbaas_loadbalancers_flavors_id')) @property def root_loadbalancer(self): return self @property def to_api_dict(self): def to_dict(sa_model, attributes): ret = {} for attr in attributes: value = getattr(sa_model, attr) ret[attr] = value return ret ret_dict = to_dict(self, [ 'id', 'tenant_id', 'name', 'description', 'vip_subnet_id', 'vip_port_id', 'vip_address', 'operating_status', 'provisioning_status', 'admin_state_up', 'flavor_id']) ret_dict['listeners'] = [{'id': listener.id} for listener in self.listeners] ret_dict['pools'] = [{'id': pool.id} for pool in self.pools] if self.provider: ret_dict['provider'] = self.provider.provider_name if not self.flavor_id: del ret_dict['flavor_id'] return ret_dict class PoolV2(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents a v2 neutron load balancer pool.""" NAME = 'pool' __tablename__ = "lbaas_pools" name = sa.Column(sa.String(255), nullable=True) description = sa.Column(sa.String(255), nullable=True) loadbalancer_id = sa.Column(sa.String(36), sa.ForeignKey( "lbaas_loadbalancers.id")) healthmonitor_id = sa.Column(sa.String(36), sa.ForeignKey("lbaas_healthmonitors.id"), unique=True, nullable=True) protocol = sa.Column(sa.Enum(*lb_const.POOL_SUPPORTED_PROTOCOLS, name="pool_protocolsv2"), nullable=False) lb_algorithm = sa.Column(sa.Enum(*lb_const.SUPPORTED_LB_ALGORITHMS, name="lb_algorithmsv2"), nullable=False) admin_state_up = sa.Column(sa.Boolean(), nullable=False) provisioning_status = sa.Column(sa.String(16), nullable=False) operating_status = sa.Column(sa.String(16), nullable=False) members = orm.relationship(MemberV2, backref=orm.backref("pool", uselist=False), cascade="all, delete-orphan") healthmonitor = orm.relationship( HealthMonitorV2, backref=orm.backref("pool", uselist=False)) session_persistence = orm.relationship( SessionPersistenceV2, uselist=False, backref=orm.backref("pool", uselist=False), cascade="all, delete-orphan") loadbalancer = orm.relationship( LoadBalancer, uselist=False, backref=orm.backref("pools", uselist=True)) @property def root_loadbalancer(self): return self.loadbalancer # No real relationship here. But we want to fake a pool having a # 'listener_id' sometimes for API back-ward compatibility purposes. @property def listener(self): if self.listeners: return self.listeners[0] else: return None @property def to_api_dict(self): def to_dict(sa_model, attributes): ret = {} for attr in attributes: value = getattr(sa_model, attr) ret[attr] = value return ret ret_dict = to_dict(self, [ 'id', 'tenant_id', 'name', 'description', 'healthmonitor_id', 'protocol', 'lb_algorithm', 'admin_state_up']) ret_dict['loadbalancers'] = [] if self.loadbalancer: ret_dict['loadbalancers'].append({'id': self.loadbalancer.id}) ret_dict['session_persistence'] = None if self.session_persistence: ret_dict['session_persistence'] = ( to_dict(self.session_persistence, [ 'type', 'cookie_name'])) ret_dict['members'] = [{'id': member.id} for member in self.members] ret_dict['listeners'] = [{'id': listener.id} for listener in self.listeners] if self.listener: ret_dict['listener_id'] = self.listener.id else: ret_dict['listener_id'] = None ret_dict['l7_policies'] = [{'id': l7_policy.id} for l7_policy in self.l7_policies] return ret_dict class SNI(model_base.BASEV2): """Many-to-many association between Listener and TLS container ids Making the SNI certificates list, ordered using the position """ NAME = 'sni' __tablename__ = "lbaas_sni" listener_id = sa.Column(sa.String(36), sa.ForeignKey("lbaas_listeners.id"), primary_key=True, nullable=False) tls_container_id = sa.Column(sa.String(128), primary_key=True, nullable=False) position = sa.Column(sa.Integer) @property def root_loadbalancer(self): return self.listener.loadbalancer class L7Rule(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents L7 Rule.""" NAME = 'l7rule' __tablename__ = "lbaas_l7rules" l7policy_id = sa.Column(sa.String(36), sa.ForeignKey("lbaas_l7policies.id"), nullable=False) type = sa.Column(sa.Enum(*lb_const.SUPPORTED_L7_RULE_TYPES, name="l7rule_typesv2"), nullable=False) compare_type = sa.Column(sa.Enum(*lb_const.SUPPORTED_L7_RULE_COMPARE_TYPES, name="l7rule_compare_typev2"), nullable=False) invert = sa.Column(sa.Boolean(), nullable=False) key = sa.Column(sa.String(255), nullable=True) value = sa.Column(sa.String(255), nullable=False) provisioning_status = sa.Column(sa.String(16), nullable=False) admin_state_up = sa.Column(sa.Boolean(), nullable=False) @property def root_loadbalancer(self): return self.policy.listener.loadbalancer @property def to_api_dict(self): def to_dict(sa_model, attributes): ret = {} for attr in attributes: value = getattr(sa_model, attr) ret[attr] = value return ret ret_dict = to_dict(self, [ 'id', 'tenant_id', 'type', 'compare_type', 'invert', 'key', 'value', 'admin_state_up']) ret_dict['policies'] = [] if self.policy: ret_dict['policies'].append({'id': self.policy.id}) return ret_dict class L7Policy(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents L7 Policy.""" NAME = 'l7policy' __tablename__ = "lbaas_l7policies" name = sa.Column(sa.String(255), nullable=True) description = sa.Column(sa.String(255), nullable=True) listener_id = sa.Column(sa.String(36), sa.ForeignKey("lbaas_listeners.id"), nullable=False) action = sa.Column(sa.Enum(*lb_const.SUPPORTED_L7_POLICY_ACTIONS, name="l7policy_action_typesv2"), nullable=False) redirect_pool_id = sa.Column(sa.String(36), sa.ForeignKey("lbaas_pools.id"), nullable=True) redirect_url = sa.Column(sa.String(255), nullable=True) position = sa.Column(sa.Integer, nullable=False) provisioning_status = sa.Column(sa.String(16), nullable=False) admin_state_up = sa.Column(sa.Boolean(), nullable=False) rules = orm.relationship( L7Rule, uselist=True, primaryjoin="L7Policy.id==L7Rule.l7policy_id", foreign_keys=[L7Rule.l7policy_id], cascade="all, delete-orphan", backref=orm.backref("policy") ) redirect_pool = orm.relationship( PoolV2, backref=orm.backref("l7_policies", uselist=True)) @property def root_loadbalancer(self): return self.listener.loadbalancer @property def to_api_dict(self): def to_dict(sa_model, attributes): ret = {} for attr in attributes: value = getattr(sa_model, attr) ret[attr] = value return ret ret_dict = to_dict(self, [ 'id', 'tenant_id', 'name', 'description', 'listener_id', 'action', 'redirect_pool_id', 'redirect_url', 'position', 'admin_state_up']) ret_dict['listeners'] = [{'id': self.listener_id}] ret_dict['rules'] = [{'id': rule.id} for rule in self.rules] if (ret_dict.get('action') == lb_const.L7_POLICY_ACTION_REDIRECT_TO_POOL): del ret_dict['redirect_url'] return ret_dict class Listener(model_base.BASEV2, model_base.HasId, model_base.HasProject): """Represents a v2 neutron listener.""" NAME = 'listener' __tablename__ = "lbaas_listeners" __table_args__ = ( sa.schema.UniqueConstraint('loadbalancer_id', 'protocol_port', name='uniq_loadbalancer_listener_port'), ) name = sa.Column(sa.String(255)) description = sa.Column(sa.String(255)) default_pool_id = sa.Column(sa.String(36), sa.ForeignKey("lbaas_pools.id"), nullable=True) loadbalancer_id = sa.Column(sa.String(36), sa.ForeignKey( "lbaas_loadbalancers.id")) protocol = sa.Column(sa.Enum(*lb_const.LISTENER_SUPPORTED_PROTOCOLS, name="listener_protocolsv2"), nullable=False) default_tls_container_id = sa.Column(sa.String(128), default=None, nullable=True) sni_containers = orm.relationship( SNI, backref=orm.backref("listener", uselist=False), uselist=True, primaryjoin="Listener.id==SNI.listener_id", order_by='SNI.position', collection_class=orderinglist.ordering_list( 'position'), foreign_keys=[SNI.listener_id], cascade="all, delete-orphan" ) protocol_port = sa.Column(sa.Integer, nullable=False) connection_limit = sa.Column(sa.Integer) admin_state_up = sa.Column(sa.Boolean(), nullable=False) provisioning_status = sa.Column(sa.String(16), nullable=False) operating_status = sa.Column(sa.String(16), nullable=False) default_pool = orm.relationship( PoolV2, backref=orm.backref("listeners")) loadbalancer = orm.relationship( LoadBalancer, backref=orm.backref("listeners", uselist=True)) l7_policies = orm.relationship( L7Policy, uselist=True, primaryjoin="Listener.id==L7Policy.listener_id", order_by="L7Policy.position", collection_class=orderinglist.ordering_list('position', count_from=1), foreign_keys=[L7Policy.listener_id], cascade="all, delete-orphan", backref=orm.backref("listener")) @property def root_loadbalancer(self): return self.loadbalancer @property def to_api_dict(self): def to_dict(sa_model, attributes): ret = {} for attr in attributes: value = getattr(sa_model, attr) ret[attr] = value return ret ret_dict = to_dict(self, [ 'id', 'tenant_id', 'name', 'description', 'default_pool_id', 'protocol', 'default_tls_container_id', 'protocol_port', 'connection_limit', 'admin_state_up']) # NOTE(blogan): Returning a list to future proof for M:N objects # that are not yet implemented. ret_dict['loadbalancers'] = [] if self.loadbalancer: ret_dict['loadbalancers'].append({'id': self.loadbalancer.id}) ret_dict['sni_container_refs'] = [container.tls_container_id for container in self.sni_containers] ret_dict['default_tls_container_ref'] = self.default_tls_container_id ret_dict['l7policies'] = [{'id': l7_policy.id} for l7_policy in self.l7_policies] return ret_dict