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:
committed by
Dan Wendlandt
parent
b636dc5389
commit
30b597a66a
@@ -28,6 +28,8 @@ XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0'
|
|||||||
|
|
||||||
FAULT_MAP = {exceptions.NotFound: webob.exc.HTTPNotFound,
|
FAULT_MAP = {exceptions.NotFound: webob.exc.HTTPNotFound,
|
||||||
exceptions.InUse: webob.exc.HTTPConflict,
|
exceptions.InUse: webob.exc.HTTPConflict,
|
||||||
|
exceptions.MacAddressGenerationFailure:
|
||||||
|
webob.exc.HTTPServiceUnavailable,
|
||||||
exceptions.StateInvalid: webob.exc.HTTPBadRequest}
|
exceptions.StateInvalid: webob.exc.HTTPBadRequest}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,11 @@ class PortInUse(InUse):
|
|||||||
"is plugged into the logical port.")
|
"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):
|
class AlreadyAttached(QuantumException):
|
||||||
message = _("Unable to plug the attachment %(att_id)s into port "
|
message = _("Unable to plug the attachment %(att_id)s into port "
|
||||||
"%(port_id)s for network %(net_id)s. The attachment is "
|
"%(port_id)s for network %(net_id)s. The attachment is "
|
||||||
@@ -115,3 +120,7 @@ class NotImplementedError(Error):
|
|||||||
class FixedIPNotAvailable(QuantumException):
|
class FixedIPNotAvailable(QuantumException):
|
||||||
message = _("Fixed IP (%(ip)s) unavailable for network "
|
message = _("Fixed IP (%(ip)s) unavailable for network "
|
||||||
"%(network_uuid)s")
|
"%(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))
|
collection = collection.filter(column.in_(value))
|
||||||
return [dict_func(c, fields) for c in collection.all()]
|
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):
|
def _make_network_dict(self, network, fields=None):
|
||||||
res = {'id': network['id'],
|
res = {'id': network['id'],
|
||||||
'name': network['name'],
|
'name': network['name'],
|
||||||
@@ -252,17 +283,22 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
|||||||
# unneeded db action if the operation raises
|
# unneeded db action if the operation raises
|
||||||
tenant_id = self._get_tenant_id_for_create(context, p)
|
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():
|
with context.session.begin():
|
||||||
network = self._get_network(context, p["network_id"])
|
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,
|
port = models_v2.Port(tenant_id=tenant_id,
|
||||||
network_id=p['network_id'],
|
network_id=p['network_id'],
|
||||||
mac_address=p['mac_address'],
|
mac_address=p['mac_address'],
|
||||||
|
|||||||
@@ -16,8 +16,11 @@
|
|||||||
import logging
|
import logging
|
||||||
import unittest
|
import unittest
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import mock
|
||||||
|
|
||||||
|
import quantum
|
||||||
from quantum.api.v2.router import APIRouter
|
from quantum.api.v2.router import APIRouter
|
||||||
|
from quantum.common import exceptions as q_exc
|
||||||
from quantum.db import api as db
|
from quantum.db import api as db
|
||||||
from quantum.tests.unit.testlib_api import create_request
|
from quantum.tests.unit.testlib_api import create_request
|
||||||
from quantum.wsgi import Serializer, JSONDeserializer
|
from quantum.wsgi import Serializer, JSONDeserializer
|
||||||
@@ -234,6 +237,38 @@ class TestPortsV2(QuantumDbPluginV2TestCase):
|
|||||||
self.assertEqual(res['port']['admin_state_up'],
|
self.assertEqual(res['port']['admin_state_up'],
|
||||||
data['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):
|
class TestNetworksV2(QuantumDbPluginV2TestCase):
|
||||||
# NOTE(cerberus): successful network update and delete are
|
# NOTE(cerberus): successful network update and delete are
|
||||||
|
|||||||
Reference in New Issue
Block a user