c4faac25de
In order to support Python 3.7, pylint has to be updated to 2.0.0 minimum. Newer versions of Pylint enforce additional checkers which can be addressed with some code refactoring rather than silently ignoring them in pylintrc; except useless-object-inheritance which is required to be silented so that we stay compatible with Python 2.x. Story: 2004073 Task: 27434 Change-Id: I52301d763797d619f195bd8a1c32bc47f1e68420
185 lines
7.2 KiB
Python
185 lines
7.2 KiB
Python
# 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 models
|
|
from oslo_utils import strutils
|
|
from oslo_utils import uuidutils
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.ext import declarative
|
|
from sqlalchemy.orm import collections
|
|
|
|
|
|
class OctaviaBase(models.ModelBase):
|
|
|
|
__data_model__ = None
|
|
|
|
@staticmethod
|
|
def _get_unique_key(obj):
|
|
"""Returns a unique key for passed object for data model building."""
|
|
# First handle all objects with their own ID, then handle subordinate
|
|
# objects.
|
|
if obj.__class__.__name__ in ['Member', 'Pool', 'LoadBalancer',
|
|
'Listener', 'Amphora', 'L7Policy',
|
|
'L7Rule', 'Flavor', 'FlavorProfile']:
|
|
return obj.__class__.__name__ + obj.id
|
|
if obj.__class__.__name__ in ['SessionPersistence', 'HealthMonitor']:
|
|
return obj.__class__.__name__ + obj.pool_id
|
|
if obj.__class__.__name__ in ['ListenerStatistics']:
|
|
return obj.__class__.__name__ + obj.listener_id + obj.amphora_id
|
|
if obj.__class__.__name__ in ['VRRPGroup', 'Vip']:
|
|
return obj.__class__.__name__ + obj.load_balancer_id
|
|
if obj.__class__.__name__ in ['AmphoraHealth']:
|
|
return obj.__class__.__name__ + obj.amphora_id
|
|
if obj.__class__.__name__ in ['SNI']:
|
|
return (obj.__class__.__name__ +
|
|
obj.listener_id + obj.tls_container_id)
|
|
if obj.__class__.__name__ in ['Quotas']:
|
|
return obj.__class__.__name__ + obj.project_id
|
|
raise NotImplementedError
|
|
|
|
def to_data_model(self, _graph_nodes=None):
|
|
"""Converts to a data model graph.
|
|
|
|
In order to make the resulting data model graph usable no matter how
|
|
many internal references are followed, we generate a complete graph of
|
|
OctaviaBase nodes connected to the object passed to this method.
|
|
|
|
:param _graph_nodes: Used only for internal recursion of this
|
|
method. Should not be called from the outside.
|
|
Contains a dictionary of all OctaviaBase type
|
|
objects in the generated graph
|
|
"""
|
|
_graph_nodes = _graph_nodes or {}
|
|
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('_')]
|
|
# Appending early, as any unique ID should be defined already and
|
|
# the rest of this object will get filled out more fully later on,
|
|
# and we need to add ourselves to the _graph_nodes before we
|
|
# attempt recursion.
|
|
dm_self = self.__data_model__(**dm_kwargs)
|
|
dm_key = self._get_unique_key(dm_self)
|
|
_graph_nodes.update({dm_key: dm_self})
|
|
for attr_name in attr_names:
|
|
attr = getattr(self, attr_name)
|
|
if isinstance(attr, OctaviaBase) and attr.__class__:
|
|
# If this attr is already in the graph node list, just
|
|
# reference it there and don't recurse.
|
|
ukey = self._get_unique_key(attr)
|
|
if ukey in _graph_nodes.keys():
|
|
setattr(dm_self, attr_name, _graph_nodes[ukey])
|
|
else:
|
|
setattr(dm_self, attr_name, attr.to_data_model(
|
|
_graph_nodes=_graph_nodes))
|
|
elif isinstance(attr, (collections.InstrumentedList, list)):
|
|
setattr(dm_self, attr_name, [])
|
|
listref = getattr(dm_self, attr_name)
|
|
for item in attr:
|
|
if isinstance(item, OctaviaBase) and item.__class__:
|
|
ukey = self._get_unique_key(item)
|
|
if ukey in _graph_nodes.keys():
|
|
listref.append(_graph_nodes[ukey])
|
|
else:
|
|
listref.append(
|
|
item.to_data_model(_graph_nodes=_graph_nodes))
|
|
elif not isinstance(item, OctaviaBase):
|
|
listref.append(item)
|
|
return dm_self
|
|
|
|
@staticmethod
|
|
def apply_filter(query, model, filters):
|
|
translated_filters = {}
|
|
child_map = {}
|
|
# Convert enabled to proper type
|
|
if 'enabled' in filters:
|
|
filters['enabled'] = strutils.bool_from_string(
|
|
filters['enabled'])
|
|
for attr, name_map in model.__v2_wsme__._child_map.items():
|
|
for k, v in name_map.items():
|
|
if attr in filters and k in filters[attr]:
|
|
child_map.setdefault(attr, {}).update(
|
|
{k: filters[attr].pop(k)})
|
|
filters.pop(attr, None)
|
|
|
|
for k, v in model.__v2_wsme__._type_to_model_map.items():
|
|
if k in filters:
|
|
translated_filters[v] = filters.pop(k)
|
|
translated_filters.update(filters)
|
|
if translated_filters:
|
|
query = query.filter_by(**translated_filters)
|
|
for k, v in child_map.items():
|
|
query = query.join(getattr(model, k)).filter_by(**v)
|
|
return query
|
|
|
|
|
|
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 an id."""
|
|
id = sa.Column(sa.String(36), primary_key=True,
|
|
default=uuidutils.generate_uuid)
|
|
|
|
|
|
class ProjectMixin(object):
|
|
"""Tenant mixin, add to subclasses that have a project."""
|
|
project_id = sa.Column(sa.String(36))
|
|
|
|
|
|
class NameMixin(object):
|
|
"""Name mixin to add to classes which need a name."""
|
|
name = sa.Column(sa.String(255), nullable=True)
|
|
|
|
|
|
class TagMixin(object):
|
|
"""Tags mixin to add to classes which need tags.
|
|
|
|
The class must realize the specified db relationship as well.
|
|
"""
|
|
|
|
@property
|
|
def tags(self):
|
|
if self._tags:
|
|
return [each_tag.tag for each_tag in self._tags]
|
|
return []
|
|
|
|
@tags.setter
|
|
def tags(self, values):
|
|
new_tags = []
|
|
if values:
|
|
for tag in values:
|
|
tag_ref = Tags()
|
|
tag_ref.resource_id = self.id
|
|
tag_ref.tag = tag
|
|
new_tags.append(tag_ref)
|
|
self._tags = new_tags
|
|
|
|
|
|
BASE = declarative.declarative_base(cls=OctaviaBase)
|
|
|
|
|
|
class Tags(BASE):
|
|
__tablename__ = "tags"
|
|
|
|
resource_id = sa.Column(sa.String(36), primary_key=True)
|
|
tag = sa.Column(sa.String(255), primary_key=True, index=True)
|