8ecb28dd09
This patch is dependent on commit I8d03528f8f45f5f50fa467b39245a513a37c5d89. It integrates the VersionedObject with the existing code. Integration revealed that using IPAddress is not correct for allowed address pairs, because the address can also represent a subnet. Another issue revealed by the integration is that we must retain the original string format passed by users through API for MAC addresses. Neither we can use IPNetworkField from oslo.versionedobjects for ip_address field because it will then always append prefix length to base network address, even if prefix length is maximum for the type of IP network (meaning, the address actually represents a single host), which is contradictory to how API currently behaves (returning mask-less addresses for /32 - for ipv4 - and /128 - for ipv6 - prefix lengths). To solve those issues, 'authentic' flavors for netaddr.EUI and netaddr.IPNetwork types are introduced. Those 'authentic' flavors attempt to retain the original string representation, as passed by the caller. Since base IPNetworkField recreates network object on coerce(), and hence looses information about the original string representation, we introduce our custom flavor of the field type that reuses the network object passed by the caller. The change for the type of ip_address field triggers hash change. Anyway, we are safe to change it without considering backwards compatibility, because the object is not used anywhere yet. Co-Authored-By: Ihar Hrachyshka <ihrachys@redhat.com> Change-Id: I3c937267ce789ed510373616713b3fa9517c18ac Partial-Bug: #1541928
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)
|
|
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
|