Ensure unique mac address allocation.
This is the first part of bug 1008029 If the port command does not contain a MAC address then Quantum will generate a random MAC address. The mac address will be saved in the database to ensure that it is not used by another port on the same network. Added mock-based test for mac exhaustion. Change-Id: I4d3fe12fd1e3c347b8e286d920a0609d0b3c4e8c
This commit is contained in:
parent
cabd706b48
commit
b6cb4316da
@ -28,6 +28,8 @@ XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0'
|
||||
|
||||
FAULT_MAP = {exceptions.NotFound: webob.exc.HTTPNotFound,
|
||||
exceptions.InUse: webob.exc.HTTPConflict,
|
||||
exceptions.MacAddressGenerationFailure:
|
||||
webob.exc.HTTPServiceUnavailable,
|
||||
exceptions.StateInvalid: webob.exc.HTTPBadRequest}
|
||||
|
||||
|
||||
|
@ -90,6 +90,11 @@ class PortInUse(InUse):
|
||||
"is plugged into the logical port.")
|
||||
|
||||
|
||||
class MacAddressInUse(InUse):
|
||||
message = _("Unable to complete operation for network %(net_id)s. "
|
||||
"The mac address %(mac)s is in use.")
|
||||
|
||||
|
||||
class AlreadyAttached(QuantumException):
|
||||
message = _("Unable to plug the attachment %(att_id)s into port "
|
||||
"%(port_id)s for network %(net_id)s. The attachment is "
|
||||
@ -115,3 +120,7 @@ class NotImplementedError(Error):
|
||||
class FixedIPNotAvailable(QuantumException):
|
||||
message = _("Fixed IP (%(ip)s) unavailable for network "
|
||||
"%(network_uuid)s")
|
||||
|
||||
|
||||
class MacAddressGenerationFailure(QuantumException):
|
||||
message = _("Unable to generate unique mac on network %(net_id)s.")
|
||||
|
@ -130,6 +130,37 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
collection = collection.filter(column.in_(value))
|
||||
return [dict_func(c, fields) for c in collection.all()]
|
||||
|
||||
@staticmethod
|
||||
def _generate_mac(context, network_id):
|
||||
# TODO(garyk) read from configuration file (CONF)
|
||||
max_retries = 16
|
||||
for i in range(max_retries):
|
||||
# TODO(garyk) read base mac from configuration file (CONF)
|
||||
mac = [0xfa, 0x16, 0x3e, random.randint(0x00, 0x7f),
|
||||
random.randint(0x00, 0xff), random.randint(0x00, 0xff)]
|
||||
mac_address = ':'.join(map(lambda x: "%02x" % x, mac))
|
||||
if QuantumDbPluginV2._check_unique_mac(context, network_id,
|
||||
mac_address):
|
||||
LOG.debug("Generated mac for network %s is %s",
|
||||
network_id, mac_address)
|
||||
return mac_address
|
||||
else:
|
||||
LOG.debug("Generated mac %s exists. Remaining attempts %s.",
|
||||
mac_address, max_retries - (i + 1))
|
||||
LOG.error("Unable to generate mac address after %s attempts",
|
||||
max_retries)
|
||||
raise q_exc.MacAddressGenerationFailure(net_id=network_id)
|
||||
|
||||
@staticmethod
|
||||
def _check_unique_mac(context, network_id, mac_address):
|
||||
mac_qry = context.session.query(models_v2.Port)
|
||||
try:
|
||||
mac_qry.filter_by(network_id=network_id,
|
||||
mac_address=mac_address).one()
|
||||
except exc.NoResultFound:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _make_network_dict(self, network, fields=None):
|
||||
res = {'id': network['id'],
|
||||
'name': network['name'],
|
||||
@ -252,17 +283,22 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
# unneeded db action if the operation raises
|
||||
tenant_id = self._get_tenant_id_for_create(context, p)
|
||||
|
||||
if p['mac_address'] == api_router.ATTR_NOT_SPECIFIED:
|
||||
#FIXME(danwent): this is exact Nova mac generation logic
|
||||
# we will want to provide more flexibility and to check
|
||||
# for uniqueness.
|
||||
mac = [0xfa, 0x16, 0x3e, random.randint(0x00, 0x7f),
|
||||
random.randint(0x00, 0xff), random.randint(0x00, 0xff)]
|
||||
p['mac_address'] = ':'.join(map(lambda x: "%02x" % x, mac))
|
||||
|
||||
with context.session.begin():
|
||||
network = self._get_network(context, p["network_id"])
|
||||
|
||||
# Ensure that a MAC address is defined and it is unique on the
|
||||
# network
|
||||
if p['mac_address'] == api_router.ATTR_NOT_SPECIFIED:
|
||||
p['mac_address'] = QuantumDbPluginV2._generate_mac(
|
||||
context, p["network_id"])
|
||||
else:
|
||||
# Ensure that the mac on the network is unique
|
||||
if not QuantumDbPluginV2._check_unique_mac(context,
|
||||
p["network_id"],
|
||||
p['mac_address']):
|
||||
raise q_exc.MacAddressInUse(net_id=p["network_id"],
|
||||
mac=p['mac_address'])
|
||||
|
||||
port = models_v2.Port(tenant_id=tenant_id,
|
||||
network_id=p['network_id'],
|
||||
mac_address=p['mac_address'],
|
||||
|
@ -16,8 +16,11 @@
|
||||
import logging
|
||||
import unittest
|
||||
import contextlib
|
||||
import mock
|
||||
|
||||
import quantum
|
||||
from quantum.api.v2.router import APIRouter
|
||||
from quantum.common import exceptions as q_exc
|
||||
from quantum.db import api as db
|
||||
from quantum.tests.unit.testlib_api import create_request
|
||||
from quantum.wsgi import Serializer, JSONDeserializer
|
||||
@ -234,6 +237,38 @@ class TestPortsV2(QuantumDbPluginV2TestCase):
|
||||
self.assertEqual(res['port']['admin_state_up'],
|
||||
data['port']['admin_state_up'])
|
||||
|
||||
def test_requested_duplicate_mac(self):
|
||||
fmt = 'json'
|
||||
with self.port() as port:
|
||||
mac = port['port']['mac_address']
|
||||
# check that MAC address matches base MAC
|
||||
# TODO(garyk) read base mac from configuration file (CONF)
|
||||
base_mac = [0xfa, 0x16, 0x3e]
|
||||
base_mac_address = ':'.join(map(lambda x: "%02x" % x, base_mac))
|
||||
self.assertTrue(mac.startswith(base_mac_address))
|
||||
kwargs = {"mac_address": mac}
|
||||
net_id = port['port']['network_id']
|
||||
res = self._create_port(fmt, net_id=net_id, **kwargs)
|
||||
port2 = self.deserialize(fmt, res)
|
||||
self.assertEquals(res.status_int, 409)
|
||||
|
||||
def test_mac_exhaustion(self):
|
||||
# rather than actually consuming all MAC (would take a LONG time)
|
||||
# we just raise the exception that would result.
|
||||
@staticmethod
|
||||
def fake_gen_mac(context, net_id):
|
||||
raise q_exc.MacAddressGenerationFailure(net_id=net_id)
|
||||
|
||||
fmt = 'json'
|
||||
with mock.patch.object(quantum.db.db_base_plugin_v2.QuantumDbPluginV2,
|
||||
'_generate_mac', new=fake_gen_mac):
|
||||
res = self._create_network(fmt=fmt, name='net1',
|
||||
admin_status_up=True)
|
||||
network = self.deserialize(fmt, res)
|
||||
net_id = network['network']['id']
|
||||
res = self._create_port(fmt, net_id=net_id)
|
||||
self.assertEquals(res.status_int, 503)
|
||||
|
||||
|
||||
class TestNetworksV2(QuantumDbPluginV2TestCase):
|
||||
# NOTE(cerberus): successful network update and delete are
|
||||
|
Loading…
Reference in New Issue
Block a user