10ada71486
Sometimes object users need access to corresponding models that are used to persist object data. While it's not encouraged, and object consumers should try to rely solely on object API and fields, we should fulfill this special need, at least for now. One of use cases to access the corresponding database model are functions registered by plugins to extend core resources. Those functions are passed into register_dict_extend_funcs and expect the model as one of its arguments. Later, when more objects are adopted in base plugin code, and we are ready to switch extensions to objects, we can pass to those functions some wrappers that would trigger deprecation warnings on attempts to access attributes that are not available on objects; and then after a while finally switch to passing objects directly instead of those wrappers. Of course, that would not happen overnight, and the path would take several cycles. To avoid the stored reference to the model to influence other code fetching from the session, we detach (expunge) the model from the active database session on every fetch. We also refresh the model before detaching it when the corresponding object had synthetic fields changed, because that's usually an indication that some relationships may be stale on the model. Since we now consistently detach the model from the active session on each fetch, we cannot reuse it. So every time we hit update, we now need to refetch the model from the session, otherwise we will hit an error trying to refresh and/or detach an already detached model. Hence the change in NeutronDbObject.update to always trigger update_object irrespective to whether any persistent fields were changed. This makes test_update_no_changes test case incorrect, hence its removal. Due to the way RBAC metaclass works, it may trigger cls.get_object in the middle of object creation (to validate newly created RBAC entry against the object). It results in duplicate expunge calls for the same object model (one during object creation, another when fetching the same object to validate it for RBAC). To avoid that, switched RBAC code from objects API to direct objects.db_api.get_object calls that will avoid triggering the whole model expunge/refresh machinery. Now that we have models stored on objects, the patch switched back plugin code to passing models in places where we previously, by mistake, were passing objects into extensions. Specifically, the switch for allowed address pairs occurred with I3c937267ce789ed510373616713b3fa9517c18ac. For subnetpools, it happened in I1415c7a29af86d377ed31cce40888631a34d4811. Neither of those was released in Mitaka, so it did not break anyone using major releases. Also, we have not heard from any trunk chaser that would be affected by the mistake. There are not other objects used in database code where we would pass them into extensions, so we should be good. Closes-Bug: #1621837 Change-Id: I130609194f15b89df89e5606fb8193849edd14d8 Partially-Implements: blueprint adopt-oslo-versioned-objects-for-db
153 lines
6.4 KiB
Python
153 lines
6.4 KiB
Python
# Copyright 2013 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_lib.api import validators
|
|
|
|
from neutron.api.v2 import attributes as attr
|
|
from neutron.db import db_base_plugin_v2
|
|
|
|
from neutron.common import utils
|
|
from neutron.extensions import allowedaddresspairs as addr_pair
|
|
from neutron.objects import base as obj_base
|
|
from neutron.objects.port.extensions import (allowedaddresspairs
|
|
as obj_addr_pair)
|
|
|
|
|
|
class AllowedAddressPairsMixin(object):
|
|
"""Mixin class for allowed address pairs."""
|
|
|
|
def _process_create_allowed_address_pairs(self, context, port,
|
|
allowed_address_pairs):
|
|
if not validators.is_attr_set(allowed_address_pairs):
|
|
return []
|
|
try:
|
|
with context.session.begin(subtransactions=True):
|
|
for address_pair in allowed_address_pairs:
|
|
# use port.mac_address if no mac address in address pair
|
|
if 'mac_address' not in address_pair:
|
|
address_pair['mac_address'] = port['mac_address']
|
|
# retain string format as passed through API
|
|
mac_address = utils.AuthenticEUI(
|
|
address_pair['mac_address'])
|
|
ip_address = utils.AuthenticIPNetwork(
|
|
address_pair['ip_address'])
|
|
pair_obj = obj_addr_pair.AllowedAddressPair(
|
|
context,
|
|
port_id=port['id'],
|
|
mac_address=mac_address,
|
|
ip_address=ip_address)
|
|
pair_obj.create()
|
|
except obj_base.NeutronDbObjectDuplicateEntry:
|
|
raise addr_pair.DuplicateAddressPairInRequest(
|
|
mac_address=address_pair['mac_address'],
|
|
ip_address=address_pair['ip_address'])
|
|
|
|
return allowed_address_pairs
|
|
|
|
def get_allowed_address_pairs(self, context, port_id):
|
|
pairs = self._get_allowed_address_pairs_objs(context, port_id)
|
|
return [self._make_allowed_address_pairs_dict(pair.db_obj)
|
|
for pair in pairs]
|
|
|
|
def _get_allowed_address_pairs_objs(self, context, port_id):
|
|
pairs = obj_addr_pair.AllowedAddressPair.get_objects(
|
|
context, port_id=port_id)
|
|
return pairs
|
|
|
|
def _extend_port_dict_allowed_address_pairs(self, port_res, port_db):
|
|
# If port_db is provided, allowed address pairs will be accessed via
|
|
# sqlalchemy models. As they're loaded together with ports this
|
|
# will not cause an extra query.
|
|
allowed_address_pairs = [
|
|
self._make_allowed_address_pairs_dict(address_pair) for
|
|
address_pair in port_db.allowed_address_pairs]
|
|
port_res[addr_pair.ADDRESS_PAIRS] = allowed_address_pairs
|
|
return port_res
|
|
|
|
# Register dict extend functions for ports
|
|
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
|
|
attr.PORTS, ['_extend_port_dict_allowed_address_pairs'])
|
|
|
|
def _delete_allowed_address_pairs(self, context, id):
|
|
pairs = self._get_allowed_address_pairs_objs(context, port_id=id)
|
|
with context.session.begin(subtransactions=True):
|
|
for pair in pairs:
|
|
pair.delete()
|
|
|
|
def _make_allowed_address_pairs_dict(self, allowed_address_pairs,
|
|
fields=None):
|
|
res = {'mac_address': allowed_address_pairs['mac_address'],
|
|
'ip_address': allowed_address_pairs['ip_address']}
|
|
return self._fields(res, fields)
|
|
|
|
def _has_address_pairs(self, port):
|
|
return (validators.is_attr_set(port['port'][addr_pair.ADDRESS_PAIRS])
|
|
and port['port'][addr_pair.ADDRESS_PAIRS] != [])
|
|
|
|
def _check_update_has_allowed_address_pairs(self, port):
|
|
"""Determine if request has an allowed address pair.
|
|
|
|
Return True if the port parameter has a non-empty
|
|
'allowed_address_pairs' attribute. Otherwise returns False.
|
|
"""
|
|
return (addr_pair.ADDRESS_PAIRS in port['port'] and
|
|
self._has_address_pairs(port))
|
|
|
|
def _check_update_deletes_allowed_address_pairs(self, port):
|
|
"""Determine if request deletes address pair.
|
|
|
|
Return True if port has an allowed address pair and its value
|
|
is either [] or not is_attr_set, otherwise return False
|
|
"""
|
|
return (addr_pair.ADDRESS_PAIRS in port['port'] and
|
|
not self._has_address_pairs(port))
|
|
|
|
def is_address_pairs_attribute_updated(self, port, update_attrs):
|
|
"""Check if the address pairs attribute is being updated.
|
|
|
|
Returns True if there is an update. This can be used to decide
|
|
if a port update notification should be sent to agents or third
|
|
party controllers.
|
|
"""
|
|
|
|
new_pairs = update_attrs.get(addr_pair.ADDRESS_PAIRS)
|
|
if new_pairs is None:
|
|
return False
|
|
old_pairs = port.get(addr_pair.ADDRESS_PAIRS)
|
|
|
|
# Missing or unchanged address pairs in attributes mean no update
|
|
return new_pairs != old_pairs
|
|
|
|
def update_address_pairs_on_port(self, context, port_id, port,
|
|
original_port, updated_port):
|
|
"""Update allowed address pairs on port.
|
|
|
|
Returns True if an update notification is required. Notification
|
|
is not done here because other changes on the port may need
|
|
notification. This method is expected to be called within
|
|
a transaction.
|
|
"""
|
|
new_pairs = port['port'].get(addr_pair.ADDRESS_PAIRS)
|
|
|
|
if self.is_address_pairs_attribute_updated(original_port,
|
|
port['port']):
|
|
updated_port[addr_pair.ADDRESS_PAIRS] = new_pairs
|
|
self._delete_allowed_address_pairs(context, port_id)
|
|
self._process_create_allowed_address_pairs(
|
|
context, updated_port, new_pairs)
|
|
return True
|
|
|
|
return False
|