FAULT_MAP = {exceptions.NotFound: webob.exc.HTTPNotFound,
exceptions.InUse: webob.exc.HTTPConflict,
+ exceptions.ResourceExhausted: webob.exc.HTTPServiceUnavailable,
exceptions.MacAddressGenerationFailure:
webob.exc.HTTPServiceUnavailable,
exceptions.StateInvalid: webob.exc.HTTPBadRequest,
class VlanIdInUse(InUse):
- message = _("Unable to complete operation for network %(net_id)s. "
+ message = _("Unable to create the network. "
"The VLAN %(vlan_id)s is in use.")
+class ResourceExhausted(QuantumException):
+ pass
+
+
+class NoNetworkAvailable(ResourceExhausted):
+ message = _("Unable to create the network. "
+ "No virtual network is available.")
+
+
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 "
raise c_exc.VlanIDNotAvailable()
-def reserve_specific_vlanid(vlan_id, net_id):
+def reserve_specific_vlanid(vlan_id):
"""Reserve a specific vlanid"""
LOG.debug("reserve_specific_vlanid() called")
if vlan_id < 1 or vlan_id > 4094:
filter_by(vlan_id=vlan_id).
one())
if rvlanid["vlan_used"]:
- raise q_exc.VlanIdInUse(net_id=net_id, vlan_id=vlan_id)
+ raise q_exc.VlanIdInUse(vlan_id=vlan_id)
LOG.debug("reserving dynamic vlanid %s" % vlan_id)
rvlanid["vlan_used"] = True
session.merge(rvlanid)
vlan_id = network['network'].get('provider:vlan_id')
if vlan_id not in (None, attributes.ATTR_NOT_SPECIFIED):
self._enforce_provider_set_auth(context, net)
- cdb.reserve_specific_vlanid(int(vlan_id), net['id'])
+ cdb.reserve_specific_vlanid(int(vlan_id))
else:
vlan_id = cdb.reserve_vlanid()
cdb.add_vlan_binding(vlan_id, net['id'])
return net
def delete_network(self, context, id):
- result = super(LinuxBridgePluginV2, self).delete_network(context, id)
vlan_binding = cdb.get_vlan_binding(id)
+ result = super(LinuxBridgePluginV2, self).delete_network(context, id)
cdb.release_vlanid(vlan_binding['vlan_id'])
return result
vlan_id = 7 # outside range dynamically allocated
with self.assertRaises(c_exc.VlanIDNotFound):
l2network_db.is_vlanid_used(vlan_id)
- l2network_db.reserve_specific_vlanid(vlan_id, "net-id")
+ l2network_db.reserve_specific_vlanid(vlan_id)
self.assertTrue(l2network_db.is_vlanid_used(vlan_id))
count = len(l2network_db.get_all_vlanids())
self.assertEqual(count, orig_count + 1)
self.assertGreater(orig_count, 0)
vlan_id = 1007 # inside range dynamically allocated
self.assertFalse(l2network_db.is_vlanid_used(vlan_id))
- l2network_db.reserve_specific_vlanid(vlan_id, "net-id")
+ l2network_db.reserve_specific_vlanid(vlan_id)
self.assertTrue(l2network_db.is_vlanid_used(vlan_id))
count = len(l2network_db.get_all_vlanids())
self.assertEqual(count, orig_count)
# License for the specific language governing permissions and limitations
# under the License.
# @author: Aaron Rosen, Nicira Networks, Inc.
+# @author: Bob Kukura, Red Hat, Inc.
+
+import logging
from sqlalchemy.orm import exc
+from quantum.common import exceptions as q_exc
import quantum.db.api as db
+from quantum.openstack.common import cfg
from quantum.plugins.openvswitch import ovs_models_v2
+LOG = logging.getLogger(__name__)
+
def get_vlans():
session = db.get_session()
except exc.NoResultFound:
pass
session.flush()
+
+
+def update_vlan_id_pool():
+ """Update vlan_ids based on current configuration."""
+
+ # determine current dynamically-allocated range
+ vlans = set(xrange(cfg.CONF.OVS.vlan_min,
+ cfg.CONF.OVS.vlan_max + 1))
+
+ session = db.get_session()
+ with session.begin(subtransactions=True):
+ # remove unused vlan_ids outside current range
+ try:
+ records = (session.query(ovs_models_v2.VlanID).
+ all())
+ for record in records:
+ try:
+ vlans.remove(record.vlan_id)
+ except KeyError:
+ if not record.vlan_used:
+ LOG.debug("removing vlan %s from pool"
+ % record.vlan_id)
+ session.delete(record)
+ except exc.NoResultFound:
+ pass
+
+ # add missing vlan_ids
+ for vlan in vlans:
+ record = ovs_models_v2.VlanID(vlan)
+ session.add(record)
+
+
+def get_vlan_id(vlan_id):
+ """Get state of specified vlan"""
+
+ session = db.get_session()
+ try:
+ record = (session.query(ovs_models_v2.VlanID).
+ filter_by(vlan_id=vlan_id).
+ one())
+ return record
+ except exc.NoResultFound:
+ return None
+
+
+def reserve_vlan_id():
+ """Reserve an unused vlan_id"""
+
+ session = db.get_session()
+ with session.begin(subtransactions=True):
+ record = (session.query(ovs_models_v2.VlanID).
+ filter_by(vlan_used=False).
+ first())
+ if not record:
+ raise q_exc.NoNetworkAvailable()
+ LOG.debug("reserving vlan %s from pool" % record.vlan_id)
+ record.vlan_used = True
+ return record.vlan_id
+
+
+def reserve_specific_vlan_id(vlan_id):
+ """Reserve a specific vlan_id"""
+
+ if vlan_id < 1 or vlan_id > 4094:
+ msg = _("Specified VLAN %s outside legal range (1-4094)") % vlan_id
+ raise q_exc.InvalidInput(error_message=msg)
+
+ session = db.get_session()
+ with session.begin(subtransactions=True):
+ try:
+ record = (session.query(ovs_models_v2.VlanID).
+ filter_by(vlan_id=vlan_id).
+ one())
+ if record.vlan_used:
+ raise q_exc.VlanIdInUse(vlan_id=vlan_id)
+ LOG.debug("reserving specific vlan %s from pool" % vlan_id)
+ record.vlan_used = True
+ except exc.NoResultFound:
+ LOG.debug("reserving specific vlan %s outside pool" % vlan_id)
+ record = ovs_models_v2.VlanID(vlan_id)
+ record.vlan_used = True
+ session.add(record)
+
+
+def release_vlan_id(vlan_id):
+ """Set the vlan state to be unused, and delete if not in range"""
+
+ session = db.get_session()
+ with session.begin(subtransactions=True):
+ try:
+ record = (session.query(ovs_models_v2.VlanID).
+ filter_by(vlan_id=vlan_id).
+ one())
+ record.vlan_used = False
+ if (vlan_id >= cfg.CONF.OVS.vlan_min and
+ vlan_id <= cfg.CONF.OVS.vlan_max):
+ LOG.debug("releasing vlan %s to pool" % vlan_id)
+ else:
+ LOG.debug("removing vlan %s outside pool" % vlan_id)
+ session.delete(record)
+ except exc.NoResultFound:
+ LOG.error("vlan id %s not found in release_vlan_id" % vlan_id)
# License for the specific language governing permissions and limitations
# under the License.
# @author: Aaron Rosen, Nicira Networks, Inc.
+# @author: Bob Kukura, Red Hat, Inc.
-from sqlalchemy import Column, Integer, String
+from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
-from quantum.db import models_v2
+from quantum.db.models_v2 import model_base
-class VlanBinding(models_v2.model_base.BASEV2):
+class VlanID(model_base.BASEV2):
+ """Represents a vlan_id usage"""
+ __tablename__ = 'vlan_ids'
+
+ vlan_id = Column(Integer, nullable=False, primary_key=True)
+ vlan_used = Column(Boolean, nullable=False)
+
+ def __init__(self, vlan_id):
+ self.vlan_id = vlan_id
+ self.vlan_used = False
+
+ def __repr__(self):
+ return "<VlanID(%d,%s)>" % (self.vlan_id, self.vlan_used)
+
+
+class VlanBinding(model_base.BASEV2):
"""Represents a binding of network_id to vlan_id."""
__tablename__ = 'vlan_bindings'
- vlan_id = Column(Integer, primary_key=True)
- network_id = Column(String(255))
+ network_id = Column(String(36), ForeignKey('networks.id',
+ ondelete="CASCADE"),
+ primary_key=True)
+ vlan_id = Column(Integer, nullable=False)
def __init__(self, vlan_id, network_id):
self.network_id = network_id
return "<VlanBinding(%s,%s)>" % (self.vlan_id, self.network_id)
-class TunnelIP(models_v2.model_base.BASEV2):
+class TunnelIP(model_base.BASEV2):
"""Represents a remote IP in tunnel mode."""
__tablename__ = 'tunnel_ips'
# Exception thrown if no more VLANs are available
class NoFreeVLANException(Exception):
+ # TODO(rkukura) Remove this class when removing V1 API
pass
class VlanMap(object):
+ # TODO(rkukura) Remove this class when removing V1 API
vlans = {}
net_ids = {}
free_vlans = set()
msg = _("Specified VLAN %s outside legal range (1-4094)") % vlan_id
raise q_exc.InvalidInput(error_message=msg)
if self.vlans.get(vlan_id):
- raise q_exc.VlanIdInUse(net_id=network_id,
- vlan_id=vlan_id)
+ raise q_exc.VlanIdInUse(vlan_id=vlan_id)
self.free_vlans.discard(vlan_id)
self.set_vlan(vlan_id, network_id)
class OVSQuantumPlugin(QuantumPluginBase):
+ # TODO(rkukura) Remove this class when removing V1 API
def __init__(self, configfile=None):
options = {"sql_connection": cfg.CONF.DATABASE.sql_connection}
options.update({"reconnect_interval": reconnect_interval})
db.configure_db(options)
- self.vmap = VlanMap(cfg.CONF.OVS.vlan_min, cfg.CONF.OVS.vlan_max)
- self.vmap.populate_already_used(ovs_db_v2.get_vlans())
+ # update the vlan_id table based on current configuration
+ ovs_db_v2.update_vlan_id_pool()
# TODO(rkukura) Use core mechanism for attribute authorization
# when available.
vlan_id = network['network'].get('provider:vlan_id')
if vlan_id not in (None, attributes.ATTR_NOT_SPECIFIED):
self._enforce_provider_set_auth(context, net)
- self.vmap.acquire_specific(int(vlan_id), str(net['id']))
+ ovs_db_v2.reserve_specific_vlan_id(vlan_id)
else:
- vlan_id = self.vmap.acquire(str(net['id']))
+ vlan_id = ovs_db_v2.reserve_vlan_id()
except Exception:
super(OVSQuantumPluginV2, self).delete_network(context, net['id'])
raise
return net
def delete_network(self, context, id):
+ vlan_id = ovs_db_v2.get_vlan(id)
result = super(OVSQuantumPluginV2, self).delete_network(context, id)
- ovs_db_v2.remove_vlan_binding(id)
- self.vmap.release(id)
+ ovs_db_v2.release_vlan_id(vlan_id)
return result
def get_network(self, context, id, fields=None, verbose=None):
--- /dev/null
+# Copyright (c) 2012 OpenStack, LLC.
+#
+# 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 unittest2
+
+from quantum.common import exceptions as q_exc
+from quantum.db import api as db
+from quantum.db import models_v2
+from quantum.plugins.openvswitch.common import config
+from quantum.openstack.common import cfg
+from quantum.plugins.openvswitch import ovs_db_v2
+
+VLAN_MIN = 10
+VLAN_MAX = 19
+
+
+class OVSVlanIdsTest(unittest2.TestCase):
+ def setUp(self):
+ cfg.CONF.set_override('vlan_min', VLAN_MIN, group='OVS')
+ cfg.CONF.set_override('vlan_max', VLAN_MAX, group='OVS')
+
+ options = {"sql_connection": cfg.CONF.DATABASE.sql_connection}
+ options.update({'base': models_v2.model_base.BASEV2})
+ sql_max_retries = cfg.CONF.DATABASE.sql_max_retries
+ options.update({"sql_max_retries": sql_max_retries})
+ reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
+ options.update({"reconnect_interval": reconnect_interval})
+ db.configure_db(options)
+
+ ovs_db_v2.update_vlan_id_pool()
+
+ def tearDown(self):
+ db.clear_db()
+ cfg.CONF.reset()
+
+ def test_update_vlan_id_pool(self):
+ self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MIN - 1))
+ self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN).vlan_used)
+ self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN + 1).vlan_used)
+ self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MAX).vlan_used)
+ self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MAX + 1))
+
+ cfg.CONF.set_override('vlan_min', VLAN_MIN + 5, group='OVS')
+ cfg.CONF.set_override('vlan_max', VLAN_MAX + 5, group='OVS')
+ ovs_db_v2.update_vlan_id_pool()
+
+ self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MIN + 5 - 1))
+ self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN + 5).vlan_used)
+ self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN + 5 + 1).vlan_used)
+ self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MAX + 5).vlan_used)
+ self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MAX + 5 + 1))
+
+ def test_vlan_id_pool(self):
+ vlan_ids = set()
+ for x in xrange(VLAN_MIN, VLAN_MAX + 1):
+ vlan_id = ovs_db_v2.reserve_vlan_id()
+ self.assertGreaterEqual(vlan_id, VLAN_MIN)
+ self.assertLessEqual(vlan_id, VLAN_MAX)
+ vlan_ids.add(vlan_id)
+
+ with self.assertRaises(q_exc.NoNetworkAvailable):
+ vlan_id = ovs_db_v2.reserve_vlan_id()
+
+ for vlan_id in vlan_ids:
+ ovs_db_v2.release_vlan_id(vlan_id)
+
+ def test_invalid_specific_vlan_id(self):
+ with self.assertRaises(q_exc.InvalidInput):
+ vlan_id = ovs_db_v2.reserve_specific_vlan_id(0)
+
+ with self.assertRaises(q_exc.InvalidInput):
+ vlan_id = ovs_db_v2.reserve_specific_vlan_id(4095)
+
+ def test_specific_vlan_id_inside_pool(self):
+ vlan_id = VLAN_MIN + 5
+ self.assertFalse(ovs_db_v2.get_vlan_id(vlan_id).vlan_used)
+ ovs_db_v2.reserve_specific_vlan_id(vlan_id)
+ self.assertTrue(ovs_db_v2.get_vlan_id(vlan_id).vlan_used)
+
+ with self.assertRaises(q_exc.VlanIdInUse):
+ ovs_db_v2.reserve_specific_vlan_id(vlan_id)
+
+ ovs_db_v2.release_vlan_id(vlan_id)
+ self.assertFalse(ovs_db_v2.get_vlan_id(vlan_id).vlan_used)
+
+ def test_specific_vlan_id_outside_pool(self):
+ vlan_id = VLAN_MAX + 5
+ self.assertIsNone(ovs_db_v2.get_vlan_id(vlan_id))
+ ovs_db_v2.reserve_specific_vlan_id(vlan_id)
+ self.assertTrue(ovs_db_v2.get_vlan_id(vlan_id).vlan_used)
+
+ with self.assertRaises(q_exc.VlanIdInUse):
+ ovs_db_v2.reserve_specific_vlan_id(vlan_id)
+
+ ovs_db_v2.release_vlan_id(vlan_id)
+ self.assertIsNone(ovs_db_v2.get_vlan_id(vlan_id))
class VlanMapTest(unittest.TestCase):
+ # TODO(rkukura) Remove this class when removing V1 API
def setUp(self):
self.vmap = VlanMap()