Uncouple lbaas object models from neutron core plugin results

Since I6e1ef53d7aae7d04a5485810cc1db0a8eb125953, subnets have
'description' field. We should accommodate for it on lbaas side.

Instead of introducing yet another field for lbaas object, make base
class filter out unknown fields in from_dict() before passing them into
__init__.

Change-Id: Ib00f61cfbc13bf934c31eb476039728830e79e92
Closes-Bug: #1554076
This commit is contained in:
Ihar Hrachyshka 2016-03-07 15:36:38 +01:00
parent e31e0d20a1
commit d3386d3e97
4 changed files with 201 additions and 18 deletions

View File

@ -37,6 +37,10 @@ from neutron_lbaas.services.loadbalancer import constants as l_const
class BaseDataModel(object):
# NOTE(ihrachys): we could reuse the list to provide a default __init__
# implementation. That would require handling custom default values though.
fields = []
def to_dict(self, **kwargs):
ret = {}
for attr in self.__dict__:
@ -62,14 +66,16 @@ class BaseDataModel(object):
@classmethod
def from_dict(cls, model_dict):
return cls(**model_dict)
fields = {k: v for k, v in model_dict.items()
if k in cls.fields}
return cls(**fields)
@classmethod
def from_sqlalchemy_model(cls, sa_model, calling_classes=None):
calling_classes = calling_classes or []
attr_mapping = vars(cls).get("attr_mapping")
instance = cls()
for attr_name in vars(instance):
for attr_name in cls.fields:
if attr_name.startswith('_'):
continue
if attr_mapping and attr_name in attr_mapping.keys():
@ -137,6 +143,8 @@ class BaseDataModel(object):
# instead of these.
class AllocationPool(BaseDataModel):
fields = ['start', 'end']
def __init__(self, start=None, end=None):
self.start = start
self.end = end
@ -144,6 +152,8 @@ class AllocationPool(BaseDataModel):
class HostRoute(BaseDataModel):
fields = ['destination', 'nexthop']
def __init__(self, destination=None, nexthop=None):
self.destination = destination
self.nexthop = nexthop
@ -151,6 +161,11 @@ class HostRoute(BaseDataModel):
class Subnet(BaseDataModel):
fields = ['id', 'name', 'tenant_id', 'network_id', 'ip_version', 'cidr',
'gateway_ip', 'enable_dhcp', 'ipv6_ra_mode', 'ipv6_address_mode',
'shared', 'dns_nameservers', 'host_routes', 'allocation_pools',
'subnetpool_id']
def __init__(self, id=None, name=None, tenant_id=None, network_id=None,
ip_version=None, cidr=None, gateway_ip=None, enable_dhcp=None,
ipv6_ra_mode=None, ipv6_address_mode=None, shared=None,
@ -180,11 +195,13 @@ class Subnet(BaseDataModel):
for route in host_routes]
model_dict['allocation_pools'] = [AllocationPool.from_dict(ap)
for ap in allocation_pools]
return Subnet(**model_dict)
return super(Subnet, cls).from_dict(model_dict)
class IPAllocation(BaseDataModel):
fields = ['port_id', 'ip_address', 'subnet_id', 'network_id']
def __init__(self, port_id=None, ip_address=None, subnet_id=None,
network_id=None):
self.port_id = port_id
@ -197,7 +214,7 @@ class IPAllocation(BaseDataModel):
subnet = model_dict.pop('subnet', None)
# TODO(blogan): add subnet to __init__. Can't do it yet because it
# causes issues with converting SA models into data models.
instance = IPAllocation(**model_dict)
instance = super(IPAllocation, cls).from_dict(model_dict)
setattr(instance, 'subnet', None)
if subnet:
setattr(instance, 'subnet', Subnet.from_dict(subnet))
@ -206,6 +223,10 @@ class IPAllocation(BaseDataModel):
class Port(BaseDataModel):
fields = ['id', 'tenant_id', 'name', 'network_id', 'mac_address',
'admin_state_up', 'status', 'device_id', 'device_owner',
'fixed_ips']
def __init__(self, id=None, tenant_id=None, name=None, network_id=None,
mac_address=None, admin_state_up=None, status=None,
device_id=None, device_owner=None, fixed_ips=None):
@ -225,11 +246,13 @@ class Port(BaseDataModel):
fixed_ips = model_dict.pop('fixed_ips', [])
model_dict['fixed_ips'] = [IPAllocation.from_dict(fixed_ip)
for fixed_ip in fixed_ips]
return Port(**model_dict)
return super(Port, cls).from_dict(model_dict)
class ProviderResourceAssociation(BaseDataModel):
fields = ['provider_name', 'resource_id']
def __init__(self, provider_name=None, resource_id=None):
self.provider_name = provider_name
self.resource_id = resource_id
@ -237,13 +260,16 @@ class ProviderResourceAssociation(BaseDataModel):
@classmethod
def from_dict(cls, model_dict):
device_driver = model_dict.pop('device_driver', None)
instance = ProviderResourceAssociation(**model_dict)
instance = super(ProviderResourceAssociation, cls).from_dict(
model_dict)
setattr(instance, 'device_driver', device_driver)
return instance
class SessionPersistence(BaseDataModel):
fields = ['pool_id', 'type', 'cookie_name', 'pool']
def __init__(self, pool_id=None, type=None, cookie_name=None,
pool=None):
self.pool_id = pool_id
@ -261,11 +287,14 @@ class SessionPersistence(BaseDataModel):
if pool:
model_dict['pool'] = Pool.from_dict(
pool)
return SessionPersistence(**model_dict)
return super(SessionPersistence, cls).from_dict(model_dict)
class LoadBalancerStatistics(BaseDataModel):
fields = ['loadbalancer_id', 'bytes_in', 'bytes_out', 'active_connections',
'total_connections', 'loadbalancer']
def __init__(self, loadbalancer_id=None, bytes_in=None, bytes_out=None,
active_connections=None, total_connections=None,
loadbalancer=None):
@ -283,6 +312,10 @@ class LoadBalancerStatistics(BaseDataModel):
class HealthMonitor(BaseDataModel):
fields = ['id', 'tenant_id', 'type', 'delay', 'timeout', 'max_retries',
'http_method', 'url_path', 'expected_codes',
'provisioning_status', 'admin_state_up', 'pool', 'name']
def __init__(self, id=None, tenant_id=None, type=None, delay=None,
timeout=None, max_retries=None, http_method=None,
url_path=None, expected_codes=None, provisioning_status=None,
@ -323,11 +356,17 @@ class HealthMonitor(BaseDataModel):
if pool:
model_dict['pool'] = Pool.from_dict(
pool)
return HealthMonitor(**model_dict)
return super(HealthMonitor, cls).from_dict(model_dict)
class Pool(BaseDataModel):
fields = ['id', 'tenant_id', 'name', 'description', 'healthmonitor_id',
'protocol', 'lb_algorithm', 'admin_state_up', 'operating_status',
'provisioning_status', 'members', 'healthmonitor',
'session_persistence', 'loadbalancer_id', 'loadbalancer',
'listener', 'listeners', 'l7_policies']
# Map deprecated attribute names to new ones.
attr_mapping = {'sessionpersistence': 'session_persistence'}
@ -410,11 +449,15 @@ class Pool(BaseDataModel):
session_persistence)
if loadbalancer:
model_dict['loadbalancer'] = LoadBalancer.from_dict(loadbalancer)
return Pool(**model_dict)
return super(Pool, cls).from_dict(model_dict)
class Member(BaseDataModel):
fields = ['id', 'tenant_id', 'pool_id', 'address', 'protocol_port',
'weight', 'admin_state_up', 'subnet_id', 'operating_status',
'provisioning_status', 'pool', 'name']
def __init__(self, id=None, tenant_id=None, pool_id=None, address=None,
protocol_port=None, weight=None, admin_state_up=None,
subnet_id=None, operating_status=None,
@ -445,10 +488,13 @@ class Member(BaseDataModel):
if pool:
model_dict['pool'] = Pool.from_dict(
pool)
return Member(**model_dict)
return super(Member, cls).from_dict(model_dict)
class SNI(BaseDataModel):
fields = ['listener_id', 'tls_container_id', 'position', 'listener']
def __init__(self, listener_id=None, tls_container_id=None,
position=None, listener=None):
self.listener_id = listener_id
@ -462,13 +508,12 @@ class SNI(BaseDataModel):
def to_api_dict(self):
return super(SNI, self).to_dict(listener=False)
@classmethod
def from_dict(cls, model_dict):
return SNI(**model_dict)
class TLSContainer(BaseDataModel):
fields = ['id', 'certificate', 'private_key', 'passphrase',
'intermediates', 'primary_cn']
def __init__(self, id=None, certificate=None, private_key=None,
passphrase=None, intermediates=None, primary_cn=None):
self.id = id
@ -481,6 +526,10 @@ class TLSContainer(BaseDataModel):
class L7Rule(BaseDataModel):
fields = ['id', 'tenant_id', 'l7policy_id', 'type', 'compare_type',
'invert', 'key', 'value', 'provisioning_status',
'admin_state_up', 'policy']
def __init__(self, id=None, tenant_id=None,
l7policy_id=None, type=None, compare_type=None, invert=None,
key=None, value=None, provisioning_status=None,
@ -514,11 +563,16 @@ class L7Rule(BaseDataModel):
policy = model_dict.pop('policy', None)
if policy:
model_dict['policy'] = L7Policy.from_dict(policy)
return L7Rule(**model_dict)
return super(L7Rule, cls).from_dict(model_dict)
class L7Policy(BaseDataModel):
fields = ['id', 'tenant_id', 'name', 'description', 'listener_id',
'action', 'redirect_pool_id', 'redirect_url', 'position',
'admin_state_up', 'provisioning_status', 'listener', 'rules',
'redirect_pool']
def __init__(self, id=None, tenant_id=None, name=None, description=None,
listener_id=None, action=None, redirect_pool_id=None,
redirect_url=None, position=None,
@ -563,11 +617,17 @@ class L7Policy(BaseDataModel):
model_dict['redirect_pool'] = Pool.from_dict(redirect_pool)
model_dict['rules'] = [L7Rule.from_dict(rule)
for rule in rules]
return L7Policy(**model_dict)
return super(L7Policy, cls).from_dict(model_dict)
class Listener(BaseDataModel):
fields = ['id', 'tenant_id', 'name', 'description', 'default_pool_id',
'loadbalancer_id', 'protocol', 'default_tls_container_id',
'sni_containers', 'protocol_port', 'connection_limit',
'admin_state_up', 'provisioning_status', 'operating_status',
'default_pool', 'loadbalancer', 'l7_policies']
def __init__(self, id=None, tenant_id=None, name=None, description=None,
default_pool_id=None, loadbalancer_id=None, protocol=None,
default_tls_container_id=None, sni_containers=None,
@ -627,11 +687,16 @@ class Listener(BaseDataModel):
model_dict['loadbalancer'] = LoadBalancer.from_dict(loadbalancer)
model_dict['l7_policies'] = [L7Policy.from_dict(policy)
for policy in l7_policies]
return Listener(**model_dict)
return super(Listener, cls).from_dict(model_dict)
class LoadBalancer(BaseDataModel):
fields = ['id', 'tenant_id', 'name', 'description', 'vip_subnet_id',
'vip_port_id', 'vip_address', 'provisioning_status',
'operating_status', 'admin_state_up', 'vip_port', 'stats',
'provider', 'listeners', 'pools', 'flavor_id']
def __init__(self, id=None, tenant_id=None, name=None, description=None,
vip_subnet_id=None, vip_port_id=None, vip_address=None,
provisioning_status=None, operating_status=None,
@ -687,7 +752,7 @@ class LoadBalancer(BaseDataModel):
if provider:
model_dict['provider'] = ProviderResourceAssociation.from_dict(
provider)
return LoadBalancer(**model_dict)
return super(LoadBalancer, cls).from_dict(model_dict)
SA_MODEL_TO_DATA_MODEL_MAP = {

View File

@ -0,0 +1,20 @@
# 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 random
import string
# NOTE(ihrachys): this function is copied from neutron tree
def get_random_string(n=10):
return ''.join(random.choice(string.ascii_lowercase) for _ in range(n))

View File

@ -153,6 +153,7 @@ class TestLoadBalancerCallbacks(
expected_lb['provider']['device_driver'] = 'dummy'
subnet = self.plugin_instance.db._core_plugin.get_subnet(
ctx, expected_lb['vip_subnet_id'])
subnet = data_models.Subnet.from_dict(subnet).to_dict()
expected_lb['vip_port']['fixed_ips'][0]['subnet'] = subnet
del expected_lb['stats']
self.assertEqual(expected_lb, load_balancer)

View File

@ -0,0 +1,97 @@
# 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 inspect
import mock
import testscenarios
from neutron_lbaas.services.loadbalancer import data_models
from neutron_lbaas.tests import base
from neutron_lbaas.tests import tools
load_tests = testscenarios.load_tests_apply_scenarios
class TestBaseDataModel(base.BaseTestCase):
def _get_fake_model_cls(self, fields_):
class FakeModel(data_models.BaseDataModel):
fields = fields_
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
return FakeModel
def test_from_dict(self):
fields_ = ['field1', 'field2']
dict_ = {field: tools.get_random_string()
for field in fields_}
model_cls = self._get_fake_model_cls(fields_)
model = model_cls.from_dict(dict_)
for field in fields_:
self.assertEqual(dict_[field], getattr(model, field))
def test_from_dict_filters_by_fields(self):
fields_ = ['field1', 'field2']
dict_ = {field: tools.get_random_string()
for field in fields_}
dict_['foo'] = 'bar'
model_cls = self._get_fake_model_cls(fields_)
model = model_cls.from_dict(dict_)
self.assertFalse(hasattr(model, 'foo'))
def _get_models():
models = []
for name, obj in inspect.getmembers(data_models):
if inspect.isclass(obj):
if issubclass(obj, data_models.BaseDataModel):
if type(obj) != data_models.BaseDataModel:
models.append(obj)
return models
class TestModels(base.BaseTestCase):
scenarios = [
(model.__name__, {'model': model})
for model in _get_models()
]
@staticmethod
def _get_iterable_mock(*args, **kwargs):
m = mock.create_autospec(dict, spec_set=True)
def _get_empty_iterator(*args, **kwargs):
return iter([])
m.__iter__ = _get_empty_iterator
m.pop = _get_empty_iterator
return m
def test_from_dict_filters_by_fields(self):
dict_ = {field: self._get_iterable_mock()
for field in self.model.fields}
dict_['foo'] = 'bar'
model = self.model.from_dict(dict_)
self.assertFalse(hasattr(model, 'foo'))