net_utils: Add a random mac generator

If you want to generate thousands of mac addresses for ports in the same
subnet, it is much more efficient to use the generator as it will
guarantee that you will not get repeated macs. It is also about 2x as
fast when not counting repetition.

The use case for this in Neutron is for making the bulk port creation
perform all the port creation at once, without having to go one by one
internally as it currently does.

Implements: blueprint speed-up-neutron-bulk-creation
Change-Id: I8551c88ffe3ddefe617fb052eb7ca6f0d7d85069
Signed-off-by: Antoni Segura Puimedon <antonisp@celebdor.com>
This commit is contained in:
Antoni Segura Puimedon 2017-10-10 12:33:19 +02:00 committed by Slawek Kaplonski
parent 7bc2bf7392
commit b8677baeb7
3 changed files with 70 additions and 0 deletions

View File

@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import itertools
import random
import socket
@ -46,6 +47,37 @@ class TestGetRandomMac(base.BaseTestCase):
mock_rnd.assert_called_with(8)
class TestRandomMacGenerator(base.BaseTestCase):
def test_all_macs_generated(self):
mac = ['aa', 'bb', 'cc', 'dd', 'ee', 'ff']
generator = itertools.islice(net.random_mac_generator(mac), 70000)
self.assertEqual(2**16, len(list(generator)))
@mock.patch.object(random, 'getrandbits', return_value=0xa2)
def test_first_generated_mac(self, mock_rnd):
mac = ['aa', 'bb', 'cc', '00', 'ee', 'ff']
generator = itertools.islice(net.random_mac_generator(mac), 1)
self.assertEqual(['aa:bb:cc:a2:a2:a2'], list(generator))
mock_rnd.assert_called_with(8)
@mock.patch.object(random, 'getrandbits', return_value=0xa2)
def test_respected_early_zeroes_generated_mac(self, mock_rnd):
mac1 = ['00', 'bb', 'cc', '00', 'ee', 'ff']
generator = itertools.islice(net.random_mac_generator(mac1), 1)
self.assertEqual(['00:bb:cc:a2:a2:a2'], list(generator))
mac2 = ['aa', '00', 'cc', '00', 'ee', 'ff']
generator = itertools.islice(net.random_mac_generator(mac2), 1)
self.assertEqual(['aa:00:cc:a2:a2:a2'], list(generator))
mac3 = ['aa', 'bb', '00', '00', 'ee', 'ff']
generator = itertools.islice(net.random_mac_generator(mac3), 1)
self.assertEqual(['aa:bb:00:a2:a2:a2'], list(generator))
mock_rnd.assert_called_with(8)
class TestPortDeviceOwner(base.BaseTestCase):
def test_is_port_trusted(self):

View File

@ -43,6 +43,36 @@ def get_random_mac(base_mac):
return ':'.join(["%02x" % x for x in mac])
def random_mac_generator(base_mac):
"""Generates random mac addresses from a specified base format.
The first 3 octets of each MAC address will remain unchanged. If the 4th
octet is not 00, it will also be used. The others will be randomly
generated.
:param base_mac: Base mac address represented by an array of 6 strings.
:returns: A mac address string generator.
"""
fixed = list(base_mac[0:3])
to_generate = 3
if base_mac[3] != '00':
fixed += base_mac[3]
to_generate = 2
beginning = ':'.join(fixed) + ':'
form = '{}' + ':'.join('{:02x}' for _ in range(to_generate))
max_macs = 2 ** (to_generate * 8)
seen = set()
while len(seen) < max_macs:
numbers = [random.getrandbits(8) for _ in range(to_generate)]
mac = form.format(beginning, *numbers)
if mac in seen:
continue
else:
seen.add(mac)
yield mac
def is_port_trusted(port):
"""Used to determine if port can be trusted not to attack network.

View File

@ -0,0 +1,8 @@
---
features:
- |
Introduced ``neutron_lib.utils.net.random_mac_generator(basemac)``. It allows
you to get a mac address string Python generator from the same kind of
basemac that ``neutron_lib.utils.net.get_random_mac(basemac)`` expects. If
there are a lot of macs to get, this will speed the process up
significantly over generating single macs and testing for collisions.