octavia/octavia/common/data_models.py

539 lines
20 KiB
Python

# Copyright (c) 2014 Rackspace
# Copyright (c) 2016 Blue Box, an IBM Company
# 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.
import re
from sqlalchemy.orm import collections
from octavia.common import constants
class BaseDataModel(object):
# NOTE(brandon-logan) This does not discover dicts for relationship
# attributes.
def to_dict(self):
"""Converts a data model to a dictionary."""
ret = {}
for attr in self.__dict__:
if attr.startswith('_'):
continue
if isinstance(getattr(self, attr), (BaseDataModel, list)):
ret[attr] = None
else:
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
@classmethod
def from_dict(cls, dict):
return cls(**dict)
@classmethod
def _name(cls):
"""Returns class name in a more human readable form."""
# Split the class name up by capitalized words
return ' '.join(re.findall('[A-Z][^A-Z]*', cls.__name__))
def _get_unique_key(self, obj=None):
"""Returns a unique key for passed object for data model building."""
obj = obj or self
# First handle all objects with their own ID, then handle subordinate
# objects.
if obj.__class__.__name__ in ['Member', 'Pool', 'LoadBalancer',
'Listener', 'Amphora', 'L7Policy',
'L7Rule']:
return obj.__class__.__name__ + obj.id
elif obj.__class__.__name__ in ['SessionPersistence', 'HealthMonitor']:
return obj.__class__.__name__ + obj.pool_id
elif obj.__class__.__name__ in ['ListenerStatistics', 'SNI']:
return obj.__class__.__name__ + obj.listener_id
elif obj.__class__.__name__ in ['VRRPGroup', 'Vip']:
return obj.__class__.__name__ + obj.load_balancer_id
elif obj.__class__.__name__ in ['AmphoraHealth']:
return obj.__class__.__name__ + obj.amphora_id
else:
raise NotImplementedError
def _find_in_graph(self, key, _visited_nodes=None):
"""Locates an object with the given unique key in the current
object graph and returns a reference to it.
"""
_visited_nodes = _visited_nodes or []
mykey = self._get_unique_key()
if mykey in _visited_nodes:
# Seen this node already, don't traverse further
return None
elif mykey == key:
return self
else:
_visited_nodes.append(mykey)
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, BaseDataModel):
result = attr._find_in_graph(
key, _visited_nodes=_visited_nodes)
if result is not None:
return result
elif isinstance(attr, (collections.InstrumentedList, list)):
for item in attr:
if isinstance(item, BaseDataModel):
result = item._find_in_graph(
key, _visited_nodes=_visited_nodes)
if result is not None:
return result
# If we are here we didn't find it.
return None
def update(self, update_dict):
"""Generic update method which works for simple,
non-relational attributes.
"""
for key, value in update_dict.items():
setattr(self, key, value)
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
def delete(self):
self.pool.session_persistence = None
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
def delete(self):
self.listener.stats = None
class HealthMonitor(BaseDataModel):
def __init__(self, id=None, project_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.project_id = project_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
def delete(self):
self.pool.health_monitor = None
class Pool(BaseDataModel):
def __init__(self, id=None, project_id=None, name=None, description=None,
protocol=None, lb_algorithm=None, enabled=None,
operating_status=None, members=None, health_monitor=None,
session_persistence=None, load_balancer_id=None,
load_balancer=None, listeners=None, l7policies=None):
self.id = id
self.project_id = project_id
self.name = name
self.description = description
self.load_balancer_id = load_balancer_id
self.load_balancer = load_balancer
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.listeners = listeners or []
self.l7policies = l7policies or []
def update(self, update_dict):
for key, value in update_dict.items():
if key == 'session_persistence':
if self.session_persistence is not None:
self.session_persistence.update(value)
else:
value.update({'pool_id': self.id})
self.session_persistence = SessionPersistence(**value)
else:
setattr(self, key, value)
def delete(self):
for listener in self.listeners:
if listener.default_pool_id == self.id:
listener.default_pool = None
listener.default_pool_id = None
for pool in listener.pools:
if pool.id == self.id:
listener.pools.remove(pool)
break
for pool in self.load_balancer.pools:
if pool.id == self.id:
self.load_balancer.pools.remove(pool)
break
for l7policy in self.l7policies:
if l7policy.redirect_pool_id == self.id:
l7policy.action = constants.L7POLICY_ACTION_REJECT
l7policy.redirect_pool = None
l7policy.redirect_pool_id = None
class Member(BaseDataModel):
def __init__(self, id=None, project_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.project_id = project_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
def delete(self):
for mem in self.pool.members:
if mem.id == self.id:
self.pool.members.remove(mem)
break
class Listener(BaseDataModel):
def __init__(self, id=None, project_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,
tls_certificate_id=None, stats=None, default_pool=None,
load_balancer=None, sni_containers=None, peer_port=None,
l7policies=None, pools=None):
self.id = id
self.project_id = project_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.tls_certificate_id = tls_certificate_id
self.stats = stats
self.default_pool = default_pool
self.load_balancer = load_balancer
self.sni_containers = sni_containers or []
self.peer_port = peer_port
self.l7policies = l7policies or []
self.pools = pools or []
def update(self, update_dict):
for key, value in update_dict.items():
setattr(self, key, value)
if key == 'default_pool_id':
if self.default_pool is not None:
l7_pool_ids = [p.redirect_pool_id for p in self.l7policies
if p.redirect_pool_id is not None and
len(p.l7rules) > 0 and p.enabled is True]
old_pool = self.default_pool
if old_pool.id not in l7_pool_ids:
self.pools.remove(old_pool)
old_pool.listeners.remove(self)
if value is not None:
pool = self._find_in_graph('Pool' + value)
if pool not in self.pools:
self.pools.append(pool)
if self not in pool.listeners:
pool.listeners.append(self)
else:
pool = None
setattr(self, 'default_pool', pool)
def delete(self):
for listener in self.load_balancer.listeners:
if listener.id == self.id:
self.load_balancer.listeners.remove(listener)
break
for pool in self.pools:
pool.listeners.remove(self)
class LoadBalancer(BaseDataModel):
def __init__(self, id=None, project_id=None, name=None, description=None,
provisioning_status=None, operating_status=None, enabled=None,
topology=None, vip=None, listeners=None, amphorae=None,
pools=None, vrrp_group=None, server_group_id=None):
self.id = id
self.project_id = project_id
self.name = name
self.description = description
self.provisioning_status = provisioning_status
self.operating_status = operating_status
self.enabled = enabled
self.vip = vip
self.vrrp_group = vrrp_group
self.topology = topology
self.listeners = listeners or []
self.amphorae = amphorae or []
self.pools = pools or []
self.server_group_id = server_group_id
class VRRPGroup(BaseDataModel):
def __init__(self, load_balancer_id=None, vrrp_group_name=None,
vrrp_auth_type=None, vrrp_auth_pass=None, advert_int=None,
smtp_server=None, smtp_connect_timeout=None,
load_balancer=None):
self.load_balancer_id = load_balancer_id
self.vrrp_group_name = vrrp_group_name
self.vrrp_auth_type = vrrp_auth_type
self.vrrp_auth_pass = vrrp_auth_pass
self.advert_int = advert_int
self.load_balancer = load_balancer
class Vip(BaseDataModel):
def __init__(self, load_balancer_id=None, ip_address=None,
subnet_id=None, port_id=None, load_balancer=None):
self.load_balancer_id = load_balancer_id
self.ip_address = ip_address
self.subnet_id = subnet_id
self.port_id = port_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 TLSContainer(BaseDataModel):
def __init__(self, id=None, primary_cn=None, certificate=None,
private_key=None, passphrase=None, intermediates=None):
self.id = id
self.primary_cn = primary_cn
self.certificate = certificate
self.private_key = private_key
self.passphrase = passphrase
self.intermediates = intermediates or []
class Amphora(BaseDataModel):
def __init__(self, id=None, load_balancer_id=None, compute_id=None,
status=None, lb_network_ip=None, vrrp_ip=None,
ha_ip=None, vrrp_port_id=None, ha_port_id=None,
load_balancer=None, role=None, cert_expiration=None,
cert_busy=False, vrrp_interface=None, vrrp_id=None,
vrrp_priority=None):
self.id = id
self.load_balancer_id = load_balancer_id
self.compute_id = compute_id
self.status = status
self.lb_network_ip = lb_network_ip
self.vrrp_ip = vrrp_ip
self.ha_ip = ha_ip
self.vrrp_port_id = vrrp_port_id
self.ha_port_id = ha_port_id
self.role = role
self.vrrp_interface = vrrp_interface
self.vrrp_id = vrrp_id
self.vrrp_priority = vrrp_priority
self.load_balancer = load_balancer
self.cert_expiration = cert_expiration
self.cert_busy = cert_busy
def delete(self):
for amphora in self.load_balancer.amphorae:
if amphora.id == self.id:
self.load_balancer.amphorae.remove(amphora)
break
class AmphoraHealth(BaseDataModel):
def __init__(self, amphora_id=None, last_update=None, busy=False):
self.amphora_id = amphora_id
self.last_update = last_update
self.busy = busy
class L7Rule(BaseDataModel):
def __init__(self, id=None, l7policy_id=None, type=None,
compare_type=None, key=None, value=None, l7policy=None,
invert=False):
self.id = id
self.l7policy_id = l7policy_id
self.type = type
self.compare_type = compare_type
self.key = key
self.value = value
self.l7policy = l7policy
self.invert = invert
def delete(self):
if len(self.l7policy.l7rules) == 1:
# l7policy should disappear from pool and listener lists. Since
# we are operating only on the data model, we can fake this by
# calling the policy's delete method.
self.l7policy.delete()
for r in self.l7policy.l7rules:
if r.id == self.id:
self.l7policy.l7rules.remove(r)
break
class L7Policy(BaseDataModel):
def __init__(self, id=None, name=None, description=None, listener_id=None,
action=None, redirect_pool_id=None, redirect_url=None,
position=None, listener=None, redirect_pool=None,
enabled=None, l7rules=None):
self.id = id
self.name = name
self.description = description
self.listener_id = listener_id
self.action = action
self.redirect_pool_id = redirect_pool_id
self.redirect_url = redirect_url
self.position = position
self.listener = listener
self.redirect_pool = redirect_pool
self.enabled = enabled
self.l7rules = l7rules or []
def _conditionally_remove_pool_links(self, pool):
"""Removes links to the given pool from parent objects.
Note this only happens if our listener isn't referencing the pool
via its default_pool or another active l7policy's redirect_pool_id.
"""
if (self.listener.default_pool is not None and
pool is not None and
pool.id != self.listener.default_pool.id and
pool in self.listener.pools):
listener_l7pools = [
p.redirect_pool for p in self.listener.l7policies
if p.redirect_pool is not None and
len(p.l7rules) > 0 and p.enabled is True and
p.id != self.id]
if pool not in listener_l7pools:
self.listener.pools.remove(pool)
pool.listeners.remove(self.listener)
def update(self, update_dict):
for key, value in update_dict.items():
if key == 'redirect_pool_id':
self._conditionally_remove_pool_links(self.redirect_pool)
self.action = constants.L7POLICY_ACTION_REDIRECT_TO_POOL
self.redirect_url = None
pool = self._find_in_graph('Pool' + value)
self.redirect_pool = pool
if len(self.l7rules) > 0 and (self.enabled is True or
('enabled' in update_dict.keys()
and update_dict['enabled']
is True)):
if pool not in self.listener.pools:
self.listener.pools.append(pool)
if self.listener not in pool.listeners:
pool.listeners.append(self.listener)
elif key == 'redirect_url':
self.action = constants.L7POLICY_ACTION_REDIRECT_TO_URL
self._conditionally_remove_pool_links(self.redirect_pool)
self.redirect_pool = None
self.redirect_pool_id = None
elif key == 'action' and value == constants.L7POLICY_ACTION_REJECT:
self.redirect_url = None
self._conditionally_remove_pool_links(self.redirect_pool)
self.redirect_pool = None
self.redirect_pool_id = None
elif key == 'position':
self.listener.l7policies.remove(self)
self.listener.l7policies.insert(value - 1, self)
elif (key == 'enabled'
and (self.action ==
constants.L7POLICY_ACTION_REDIRECT_TO_POOL
or ('action' in update_dict.keys()
and update_dict['action'] ==
constants.L7POLICY_ACTION_REDIRECT_TO_POOL))
and (self.redirect_pool is not None
or ('redirect_pool_id' in update_dict.keys() and
self._find_in_graph(
'Pool' + update_dict['redirect_pool_id'])
is not None))):
if self.redirect_pool is None:
self.redirect_pool = self._find_in_graph(
'Pool' + update_dict['redirect_pool_id'])
self.listener.pools.append(self.redirect_pool)
self.redirect_pool.listeners.append(self.listener)
setattr(self, key, value)
def delete(self):
self._conditionally_remove_pool_links(self.redirect_pool)
if self.redirect_pool:
for p in self.redirect_pool.l7policies:
if p.id == self.id:
self.redirect_pool.l7policies.remove(p)
for p in self.listener.l7policies:
if p.id == self.id:
self.listener.l7policies.remove(p)
break