rehome plugin utils
This patch rehomes a bulk of neutron.plugins.common.utils into neutron-lib. What's left in the neutron plugin utils will be made private upon consumption. In addition this patch also enhances the is_valid_* functions of the utils to ensure the range value is a number. The previous implementation allowed passing boolean values for tunnel ranges. UTs and a release note are also included. For a sample consumption patch see Ifcb39c08706d7a12f3ebd5865eb6d2edb95eed45 Change-Id: Iabb155b5d2d0ec6104ebee5dd42cf292bdf3ec61
This commit is contained in:
parent
f1ea0172da
commit
3dfa6229d1
@ -539,3 +539,17 @@ class InvalidServiceType(InvalidInput):
|
||||
:param service_type: The service type that's invalid.
|
||||
"""
|
||||
message = _("Invalid service type: %(service_type)s.")
|
||||
|
||||
|
||||
class NetworkVlanRangeError(NeutronException):
|
||||
message = _("Invalid network VLAN range: '%(vlan_range)s' - '%(error)s'.")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# Convert vlan_range tuple to 'start:end' format for display
|
||||
if isinstance(kwargs['vlan_range'], tuple):
|
||||
kwargs['vlan_range'] = "%d:%d" % kwargs['vlan_range']
|
||||
super(NetworkVlanRangeError, self).__init__(**kwargs)
|
||||
|
||||
|
||||
class PhysicalNetworkNameError(NeutronException):
|
||||
message = _("Empty physical network name.")
|
||||
|
288
neutron_lib/plugins/utils.py
Normal file
288
neutron_lib/plugins/utils.py
Normal file
@ -0,0 +1,288 @@
|
||||
# Copyright 2013 Cisco Systems, Inc.
|
||||
#
|
||||
# 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 collections
|
||||
import contextlib
|
||||
import hashlib
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import excutils
|
||||
|
||||
from neutron_lib._i18n import _
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import exceptions
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
INTERFACE_HASH_LEN = 6
|
||||
|
||||
|
||||
def _is_valid_range(val, min, max):
|
||||
try:
|
||||
# NOTE: use str value to not permit booleans
|
||||
val = int(str(val))
|
||||
return min <= val <= max
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
|
||||
def is_valid_vlan_tag(vlan):
|
||||
"""Validate a VLAN tag.
|
||||
|
||||
:param vlan: The VLAN tag to validate.
|
||||
:returns: True if vlan is a number that is a valid VLAN tag.
|
||||
"""
|
||||
return _is_valid_range(
|
||||
vlan, constants.MIN_VLAN_TAG, constants.MAX_VLAN_TAG)
|
||||
|
||||
|
||||
def is_valid_gre_id(gre_id):
|
||||
"""Validate a GRE ID.
|
||||
|
||||
:param gre_id: The GRE ID to validate.
|
||||
:returns: True if gre_id is a number that's a valid GRE ID.
|
||||
"""
|
||||
return _is_valid_range(
|
||||
gre_id, constants.MIN_GRE_ID, constants.MAX_GRE_ID)
|
||||
|
||||
|
||||
def is_valid_vxlan_vni(vni):
|
||||
"""Validate a VXLAN VNI.
|
||||
|
||||
:param vni: The VNI to validate.
|
||||
:returns: True if vni is a number that's a valid VXLAN VNI.
|
||||
"""
|
||||
return _is_valid_range(
|
||||
vni, constants.MIN_VXLAN_VNI, constants.MAX_VXLAN_VNI)
|
||||
|
||||
|
||||
def is_valid_geneve_vni(vni):
|
||||
"""Validate a Geneve VNI
|
||||
|
||||
:param vni: The VNI to validate.
|
||||
:returns: True if vni is a number that's a valid Geneve VNI.
|
||||
"""
|
||||
return _is_valid_range(
|
||||
vni, constants.MIN_GENEVE_VNI, constants.MAX_GENEVE_VNI)
|
||||
|
||||
|
||||
_TUNNEL_MAPPINGS = {
|
||||
constants.TYPE_GRE: is_valid_gre_id,
|
||||
constants.TYPE_VXLAN: is_valid_vxlan_vni,
|
||||
constants.TYPE_GENEVE: is_valid_geneve_vni
|
||||
}
|
||||
|
||||
|
||||
def verify_tunnel_range(tunnel_range, tunnel_type):
|
||||
"""Verify a given tunnel range is valid given it's tunnel type.
|
||||
|
||||
Existing validation is done for GRE, VXLAN and GENEVE types as per
|
||||
_TUNNEL_MAPPINGS.
|
||||
|
||||
:param tunnel_range: An iterable who's 0 index is the min tunnel range
|
||||
and who's 1 index is the max tunnel range.
|
||||
:param tunnel_type: The tunnel type of the range.
|
||||
:returns: None if the tunnel_range is valid.
|
||||
:raises: NetworkTunnelRangeError if tunnel_range is invalid.
|
||||
"""
|
||||
if tunnel_type in _TUNNEL_MAPPINGS:
|
||||
for ident in tunnel_range:
|
||||
if not _TUNNEL_MAPPINGS[tunnel_type](ident):
|
||||
raise exceptions.NetworkTunnelRangeError(
|
||||
tunnel_range=tunnel_range,
|
||||
error=_("%(id)s is not a valid %(type)s identifier") %
|
||||
{'id': ident, 'type': tunnel_type})
|
||||
if tunnel_range[1] < tunnel_range[0]:
|
||||
raise exceptions.NetworkTunnelRangeError(
|
||||
tunnel_range=tunnel_range,
|
||||
error=_("End of tunnel range is less "
|
||||
"than start of tunnel range"))
|
||||
|
||||
|
||||
def _raise_invalid_tag(vlan_str, vlan_range):
|
||||
"""Raise an exception for invalid tag."""
|
||||
raise exceptions.NetworkVlanRangeError(
|
||||
vlan_range=vlan_range,
|
||||
error=_("%s is not a valid VLAN tag") % vlan_str)
|
||||
|
||||
|
||||
def verify_vlan_range(vlan_range):
|
||||
"""Verify a VLAN range is valid.
|
||||
|
||||
:param vlan_range: An iterable who's 0 index is the min tunnel range
|
||||
and who's 1 index is the max tunnel range.
|
||||
:returns: None if the vlan_range is valid.
|
||||
:raises: NetworkVlanRangeError if vlan_range is not valid.
|
||||
"""
|
||||
for vlan_tag in vlan_range:
|
||||
if not is_valid_vlan_tag(vlan_tag):
|
||||
_raise_invalid_tag(str(vlan_tag), vlan_range)
|
||||
if vlan_range[1] < vlan_range[0]:
|
||||
raise exceptions.NetworkVlanRangeError(
|
||||
vlan_range=vlan_range,
|
||||
error=_("End of VLAN range is less than start of VLAN range"))
|
||||
|
||||
|
||||
def parse_network_vlan_range(network_vlan_range):
|
||||
"""Parse a well formed network VLAN range string.
|
||||
|
||||
The network VLAN range string has the format:
|
||||
network[:vlan_begin:vlan_end]
|
||||
|
||||
:param network_vlan_range: The network VLAN range string to parse.
|
||||
:returns: A tuple who's 1st element is the network name and 2nd
|
||||
element is the VLAN range parsed from network_vlan_range.
|
||||
:raises: NetworkVlanRangeError if network_vlan_range is malformed.
|
||||
PhysicalNetworkNameError if network_vlan_range is missing a network
|
||||
name.
|
||||
"""
|
||||
entry = network_vlan_range.strip()
|
||||
if ':' in entry:
|
||||
if entry.count(':') != 2:
|
||||
raise exceptions.NetworkVlanRangeError(
|
||||
vlan_range=entry,
|
||||
error=_("Need exactly two values for VLAN range"))
|
||||
network, vlan_min, vlan_max = entry.split(':')
|
||||
if not network:
|
||||
raise exceptions.PhysicalNetworkNameError()
|
||||
|
||||
try:
|
||||
vlan_min = int(vlan_min)
|
||||
except ValueError:
|
||||
_raise_invalid_tag(vlan_min, entry)
|
||||
|
||||
try:
|
||||
vlan_max = int(vlan_max)
|
||||
except ValueError:
|
||||
_raise_invalid_tag(vlan_max, entry)
|
||||
|
||||
vlan_range = (vlan_min, vlan_max)
|
||||
verify_vlan_range(vlan_range)
|
||||
return network, vlan_range
|
||||
else:
|
||||
return entry, None
|
||||
|
||||
|
||||
def parse_network_vlan_ranges(network_vlan_ranges_cfg_entries):
|
||||
"""Parse a list of well formed network VLAN range string.
|
||||
|
||||
Behaves like parse_network_vlan_range, but parses a list of
|
||||
network VLAN strings into an ordered dict.
|
||||
|
||||
:param network_vlan_ranges_cfg_entries: The list of network VLAN
|
||||
strings to parse.
|
||||
:returns: An OrderedDict who's keys are network names and values are
|
||||
the list of VLAN ranges parsed.
|
||||
:raises: See parse_network_vlan_range.
|
||||
"""
|
||||
networks = collections.OrderedDict()
|
||||
for entry in network_vlan_ranges_cfg_entries:
|
||||
network, vlan_range = parse_network_vlan_range(entry)
|
||||
if vlan_range:
|
||||
networks.setdefault(network, []).append(vlan_range)
|
||||
else:
|
||||
networks.setdefault(network, [])
|
||||
return networks
|
||||
|
||||
|
||||
def in_pending_status(status):
|
||||
"""Return True if status is a form of pending"""
|
||||
return status in (constants.PENDING_CREATE,
|
||||
constants.PENDING_UPDATE,
|
||||
constants.PENDING_DELETE)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def delete_port_on_error(core_plugin, context, port_id):
|
||||
"""A decorator that deletes a port upon exception.
|
||||
|
||||
This decorator can be used to wrap a block of code that
|
||||
should delete a port if an exception is raised during the block's
|
||||
execution.
|
||||
|
||||
:param core_plugin: The core plugin implementing the delete_port method to
|
||||
call.
|
||||
:param context: The context.
|
||||
:param port_id: The port's ID.
|
||||
:returns: None
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
try:
|
||||
core_plugin.delete_port(context, port_id,
|
||||
l3_port_check=False)
|
||||
except exceptions.PortNotFound:
|
||||
LOG.debug("Port %s not found", port_id)
|
||||
except Exception:
|
||||
LOG.exception("Failed to delete port: %s", port_id)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def update_port_on_error(core_plugin, context, port_id, revert_value):
|
||||
"""A decorator that updates a port upon exception.
|
||||
|
||||
This decorator can be used to wrap a block of code that
|
||||
should update a port if an exception is raised during the block's
|
||||
execution.
|
||||
|
||||
:param core_plugin: The core plugin implementing the update_port method to
|
||||
call.
|
||||
:param context: The context.
|
||||
:param port_id: The port's ID.
|
||||
:param revert_value: The value to revert on the port object.
|
||||
:returns: None
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
try:
|
||||
core_plugin.update_port(context, port_id,
|
||||
{'port': revert_value})
|
||||
except Exception:
|
||||
LOG.exception("Failed to update port: %s", port_id)
|
||||
|
||||
|
||||
def get_interface_name(name, prefix='', max_len=constants.DEVICE_NAME_MAX_LEN):
|
||||
"""Construct an interface name based on the prefix and name.
|
||||
|
||||
The interface name can not exceed the maximum length passed in. Longer
|
||||
names are hashed to help ensure uniqueness.
|
||||
"""
|
||||
requested_name = prefix + name
|
||||
|
||||
if len(requested_name) <= max_len:
|
||||
return requested_name
|
||||
|
||||
# We can't just truncate because interfaces may be distinguished
|
||||
# by an ident at the end. A hash over the name should be unique.
|
||||
# Leave part of the interface name on for easier identification
|
||||
if (len(prefix) + INTERFACE_HASH_LEN) > max_len:
|
||||
raise ValueError(_("Too long prefix provided. New name would exceed "
|
||||
"given length for an interface name."))
|
||||
|
||||
namelen = max_len - len(prefix) - INTERFACE_HASH_LEN
|
||||
hashed_name = hashlib.sha1(encodeutils.to_utf8(name))
|
||||
new_name = ('%(prefix)s%(truncated)s%(hash)s' %
|
||||
{'prefix': prefix, 'truncated': name[0:namelen],
|
||||
'hash': hashed_name.hexdigest()[0:INTERFACE_HASH_LEN]})
|
||||
LOG.info("The requested interface name %(requested_name)s exceeds the "
|
||||
"%(limit)d character limitation. It was shortened to "
|
||||
"%(new_name)s to fit.",
|
||||
{'requested_name': requested_name,
|
||||
'limit': max_len, 'new_name': new_name})
|
||||
return new_name
|
209
neutron_lib/tests/unit/plugins/test_utils.py
Normal file
209
neutron_lib/tests/unit/plugins/test_utils.py
Normal file
@ -0,0 +1,209 @@
|
||||
# Copyright (c) 2015 IBM Corp.
|
||||
#
|
||||
# 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 hashlib
|
||||
|
||||
import mock
|
||||
from oslo_utils import excutils
|
||||
|
||||
from neutron_lib import constants
|
||||
from neutron_lib import exceptions
|
||||
from neutron_lib.plugins import utils
|
||||
from neutron_lib.tests import _base as base
|
||||
|
||||
|
||||
LONG_NAME1 = "A_REALLY_LONG_INTERFACE_NAME1"
|
||||
LONG_NAME2 = "A_REALLY_LONG_INTERFACE_NAME2"
|
||||
SHORT_NAME = "SHORT"
|
||||
MOCKED_HASH = "mockedhash"
|
||||
|
||||
|
||||
class MockSHA(object):
|
||||
def hexdigest(self):
|
||||
return MOCKED_HASH
|
||||
|
||||
|
||||
class TestUtils(base.BaseTestCase):
|
||||
|
||||
def test_is_valid_vlan_tag(self):
|
||||
for v in [constants.MIN_VLAN_TAG, constants.MIN_VLAN_TAG + 2,
|
||||
constants.MAX_VLAN_TAG, constants.MAX_VLAN_TAG - 2]:
|
||||
self.assertTrue(utils.is_valid_vlan_tag(v))
|
||||
|
||||
def test_is_valid_vlan_tag_invalid_data(self):
|
||||
for v in [constants.MIN_VLAN_TAG - 1, constants.MIN_VLAN_TAG - 2,
|
||||
constants.MAX_VLAN_TAG + 1, constants.MAX_VLAN_TAG + 2]:
|
||||
self.assertFalse(utils.is_valid_vlan_tag(v))
|
||||
|
||||
def test_verify_vlan_range(self):
|
||||
for v in [(constants.MIN_VLAN_TAG, constants.MIN_VLAN_TAG + 2),
|
||||
(constants.MIN_VLAN_TAG + 2, constants.MAX_VLAN_TAG - 2)]:
|
||||
self.assertIsNone(utils.verify_vlan_range(v))
|
||||
|
||||
def test_verify_vlan_range_invalid_range(self):
|
||||
for v in [(constants.MIN_VLAN_TAG, constants.MAX_VLAN_TAG + 2),
|
||||
(constants.MIN_VLAN_TAG + 4, constants.MIN_VLAN_TAG + 1)]:
|
||||
self.assertRaises(exceptions.NetworkVlanRangeError,
|
||||
utils.verify_vlan_range, v)
|
||||
|
||||
def test_parse_network_vlan_range(self):
|
||||
self.assertEqual(
|
||||
('n1', (1, 3)),
|
||||
utils.parse_network_vlan_range('n1:1:3'))
|
||||
self.assertEqual(
|
||||
('n1', (1, 1111)),
|
||||
utils.parse_network_vlan_range('n1:1:1111'))
|
||||
|
||||
def test_parse_network_vlan_range_invalid_range(self):
|
||||
self.assertRaises(exceptions.NetworkVlanRangeError,
|
||||
utils.parse_network_vlan_range,
|
||||
'n1:1,4')
|
||||
|
||||
def test_parse_network_vlan_range_missing_network(self):
|
||||
self.assertRaises(exceptions.PhysicalNetworkNameError,
|
||||
utils.parse_network_vlan_range,
|
||||
':1:4')
|
||||
|
||||
def test_parse_network_vlan_range_invalid_min_type(self):
|
||||
self.assertRaises(exceptions.NetworkVlanRangeError,
|
||||
utils.parse_network_vlan_range,
|
||||
'n1:a:4')
|
||||
|
||||
def test_parse_network_vlan_ranges(self):
|
||||
ranges = utils.parse_network_vlan_ranges(['n1:1:3', 'n2:2:4'])
|
||||
self.assertEqual(2, len(ranges.keys()))
|
||||
self.assertIn('n1', ranges.keys())
|
||||
self.assertIn('n2', ranges.keys())
|
||||
self.assertEqual(2, len(ranges['n1'][0]))
|
||||
self.assertEqual(1, ranges['n1'][0][0])
|
||||
self.assertEqual(3, ranges['n1'][0][1])
|
||||
self.assertEqual(2, len(ranges['n2'][0]))
|
||||
self.assertEqual(2, ranges['n2'][0][0])
|
||||
self.assertEqual(4, ranges['n2'][0][1])
|
||||
|
||||
def test_is_valid_gre_id(self):
|
||||
for v in [constants.MIN_GRE_ID, constants.MIN_GRE_ID + 2,
|
||||
constants.MAX_GRE_ID, constants.MAX_GRE_ID - 2]:
|
||||
self.assertTrue(utils.is_valid_gre_id(v))
|
||||
|
||||
def test_is_valid_gre_id_invalid_id(self):
|
||||
for v in [constants.MIN_GRE_ID - 1, constants.MIN_GRE_ID - 2,
|
||||
True, 'z', 99.999, []]:
|
||||
self.assertFalse(utils.is_valid_gre_id(v))
|
||||
|
||||
def test_is_valid_vxlan_vni(self):
|
||||
for v in [constants.MIN_VXLAN_VNI, constants.MAX_VXLAN_VNI,
|
||||
constants.MIN_VXLAN_VNI + 1, constants.MAX_VXLAN_VNI - 1]:
|
||||
self.assertTrue(utils.is_valid_vxlan_vni(v))
|
||||
|
||||
def test_is_valid_vxlan_vni_invalid_values(self):
|
||||
for v in [constants.MIN_VXLAN_VNI - 1, constants.MAX_VXLAN_VNI + 1,
|
||||
True, 'a', False, {}]:
|
||||
self.assertFalse(utils.is_valid_vxlan_vni(v))
|
||||
|
||||
def test_is_valid_geneve_vni(self):
|
||||
for v in [constants.MIN_GENEVE_VNI, constants.MAX_GENEVE_VNI,
|
||||
constants.MIN_GENEVE_VNI + 1, constants.MAX_GENEVE_VNI - 1]:
|
||||
self.assertTrue(utils.is_valid_geneve_vni(v))
|
||||
|
||||
def test_is_valid_geneve_vni_invalid_values(self):
|
||||
for v in [constants.MIN_GENEVE_VNI - 1, constants.MAX_GENEVE_VNI + 1,
|
||||
True, False, (), 'True']:
|
||||
self.assertFalse(utils.is_valid_geneve_vni(v))
|
||||
|
||||
def test_verify_tunnel_range_known_tunnel_type(self):
|
||||
mock_fns = [mock.Mock(return_value=False) for _ in range(3)]
|
||||
mock_map = {
|
||||
constants.TYPE_GRE: mock_fns[0],
|
||||
constants.TYPE_VXLAN: mock_fns[1],
|
||||
constants.TYPE_GENEVE: mock_fns[2]
|
||||
}
|
||||
|
||||
with mock.patch.dict(utils._TUNNEL_MAPPINGS, mock_map):
|
||||
for t in [constants.TYPE_GRE, constants.TYPE_VXLAN,
|
||||
constants.TYPE_GENEVE]:
|
||||
self.assertRaises(
|
||||
exceptions.NetworkTunnelRangeError,
|
||||
utils.verify_tunnel_range, [0, 1], t)
|
||||
for f in mock_fns:
|
||||
f.assert_called_once_with(0)
|
||||
|
||||
def test_verify_tunnel_range_invalid_range(self):
|
||||
for r in [[1, 0], [0, -1], [2, 1]]:
|
||||
self.assertRaises(
|
||||
exceptions.NetworkTunnelRangeError,
|
||||
utils.verify_tunnel_range, r, constants.TYPE_FLAT)
|
||||
|
||||
def test_verify_tunnel_range(self):
|
||||
for r in [[0, 1], [-1, 0], [1, 2]]:
|
||||
self.assertIsNone(
|
||||
utils.verify_tunnel_range(r, constants.TYPE_FLAT))
|
||||
|
||||
def test_delete_port_on_error(self):
|
||||
core_plugin = mock.Mock()
|
||||
with mock.patch.object(excutils, 'save_and_reraise_exception'):
|
||||
with mock.patch.object(utils, 'LOG'):
|
||||
with utils.delete_port_on_error(core_plugin, 'ctx', '1'):
|
||||
raise Exception()
|
||||
|
||||
core_plugin.delete_port.assert_called_once_with(
|
||||
'ctx', '1', l3_port_check=False)
|
||||
|
||||
def test_update_port_on_error(self):
|
||||
core_plugin = mock.Mock()
|
||||
with mock.patch.object(excutils, 'save_and_reraise_exception'):
|
||||
with mock.patch.object(utils, 'LOG'):
|
||||
with utils.update_port_on_error(core_plugin, 'ctx', '1', '2'):
|
||||
raise Exception()
|
||||
|
||||
core_plugin.update_port.assert_called_once_with(
|
||||
'ctx', '1', {'port': '2'})
|
||||
|
||||
@mock.patch.object(hashlib, 'sha1', return_value=MockSHA())
|
||||
def test_get_interface_name(self, mock_sha1):
|
||||
prefix = "pre-"
|
||||
prefix_long = "long_prefix"
|
||||
prefix_exceeds_max_dev_len = "much_too_long_prefix"
|
||||
hash_used = MOCKED_HASH[0:6]
|
||||
|
||||
self.assertEqual("A_REALLY_" + hash_used,
|
||||
utils.get_interface_name(LONG_NAME1))
|
||||
self.assertEqual("SHORT",
|
||||
utils.get_interface_name(SHORT_NAME))
|
||||
self.assertEqual("pre-A_REA" + hash_used,
|
||||
utils.get_interface_name(LONG_NAME1, prefix=prefix))
|
||||
self.assertEqual("pre-SHORT",
|
||||
utils.get_interface_name(SHORT_NAME, prefix=prefix))
|
||||
# len(prefix) > max_device_len - len(hash_used)
|
||||
self.assertRaises(ValueError, utils.get_interface_name, SHORT_NAME,
|
||||
prefix_long)
|
||||
# len(prefix) > max_device_len
|
||||
self.assertRaises(ValueError, utils.get_interface_name, SHORT_NAME,
|
||||
prefix=prefix_exceeds_max_dev_len)
|
||||
|
||||
def test_get_interface_uniqueness(self):
|
||||
prefix = "prefix-"
|
||||
if_prefix1 = utils.get_interface_name(LONG_NAME1, prefix=prefix)
|
||||
if_prefix2 = utils.get_interface_name(LONG_NAME2, prefix=prefix)
|
||||
self.assertNotEqual(if_prefix1, if_prefix2)
|
||||
|
||||
@mock.patch.object(hashlib, 'sha1', return_value=MockSHA())
|
||||
def test_get_interface_max_len(self, mock_sha1):
|
||||
self.assertEqual(constants.DEVICE_NAME_MAX_LEN,
|
||||
len(utils.get_interface_name(LONG_NAME1)))
|
||||
self.assertEqual(10, len(utils.get_interface_name(LONG_NAME1,
|
||||
max_len=10)))
|
||||
self.assertEqual(12, len(utils.get_interface_name(LONG_NAME1,
|
||||
prefix="pre-",
|
||||
max_len=12)))
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- The publically consumed API's from ``neutron.plugins.common.utils`` are
|
||||
now available in ``neutron_lib.plugins.utils``.
|
||||
- The ``NetworkVlanRangeError`` and ``PhysicalNetworkNameError`` exception
|
||||
classes are now available in ``neutron_lib.exceptions``.
|
Loading…
Reference in New Issue
Block a user