"admin_or_owner": [["role:admin"], ["tenant_id:%(tenant_id)s"]],
"default": [["rule:admin_or_owner"]],
+ "admin_api": [["role:admin"]],
+ "extension:provider_network:view": [["rule:admin_api"]],
+ "extension:provider_network:set": [["rule:admin_api"]],
+
"create_subnet": [],
"get_subnet": [["rule:admin_or_owner"]],
"update_subnet": [["rule:admin_or_owner"]],
for attr, attr_vals in self._attr_info.iteritems():
# Convert values if necessary
if ('convert_to' in attr_vals and
- attr in res_dict):
+ attr in res_dict and
+ res_dict[attr] != attributes.ATTR_NOT_SPECIFIED):
res_dict[attr] = attr_vals['convert_to'](res_dict[attr])
# Check that configured values are correct
"The IP address %(ip_address)s is in use.")
+class VlanIdInUse(InUse):
+ message = _("Unable to complete operation for network %(net_id)s. "
+ "The VLAN %(vlan_id)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 "
--- /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.
+
+from quantum.api.v2 import attributes
+
+EXTENDED_ATTRIBUTES_2_0 = {
+ 'networks': {
+ # TODO(rkukura): specify validation
+ 'provider:vlan_id': {'allow_post': True, 'allow_put': False,
+ 'convert_to': int,
+ 'default': attributes.ATTR_NOT_SPECIFIED,
+ 'is_visible': True},
+ }
+}
+
+
+class Providernet(object):
+ """Extension class supporting provider networks.
+
+ This class is used by quantum's extension framework to make
+ metadata about the provider network extension available to
+ clients. No new resources are defined by this extension. Instead,
+ the existing network resource's request and response messages are
+ extended with attributes in the provider namespace.
+
+ To create a provider VLAN network using the CLI with admin rights:
+
+ (shell) net-create --tenant_id <tenant-id> <net-name> \
+ --provider:vlan_id <vlan-id>
+
+ With admin rights, network dictionaries returned from CLI commands
+ will also include provider attributes.
+ """
+
+ def get_name(cls):
+ return "Provider Network"
+
+ def get_alias(cls):
+ return "provider"
+
+ def get_description(cls):
+ return "Expose mapping of virtual networks to VLANs and flat networks"
+
+ def get_namespace(cls):
+ return "http://docs.openstack.org/ext/provider/api/v1.0"
+
+ def get_updated(cls):
+ return "2012-07-23T10:00:00-00:00"
+
+ def get_extended_attributes(self, version):
+ if version == "2.0":
+ return EXTENDED_ATTRIBUTES_2_0
+ else:
+ return {}
def create_vlanids():
- """Prepopulates the vlan_bindings table"""
+ """Prepopulate the vlan_bindings table"""
LOG.debug("create_vlanids() called")
session = db.get_session()
start = CONF.VLANS.vlan_start
def get_all_vlanids():
- """Gets all the vlanids"""
+ """Get all the vlanids"""
LOG.debug("get_all_vlanids() called")
session = db.get_session()
try:
def is_vlanid_used(vlan_id):
- """Checks if a vlanid is in use"""
+ """Check if a vlanid is in use"""
LOG.debug("is_vlanid_used() called")
session = db.get_session()
try:
def release_vlanid(vlan_id):
- """Sets the vlanid state to be unused"""
+ """Set the vlanid state to be unused, and delete if not in range"""
LOG.debug("release_vlanid() called")
session = db.get_session()
try:
filter_by(vlan_id=vlan_id).
one())
vlanid["vlan_used"] = False
- session.merge(vlanid)
+ if vlan_id >= CONF.VLANS.vlan_start and vlan_id <= CONF.VLANS.vlan_end:
+ session.merge(vlanid)
+ else:
+ session.delete(vlanid)
session.flush()
return vlanid["vlan_used"]
except exc.NoResultFound:
def delete_vlanid(vlan_id):
- """Deletes a vlanid entry from db"""
+ """Delete a vlanid entry from db"""
LOG.debug("delete_vlanid() called")
session = db.get_session()
try:
def reserve_vlanid():
- """Reserves the first unused vlanid"""
+ """Reserve the first unused vlanid"""
LOG.debug("reserve_vlanid() called")
session = db.get_session()
try:
raise c_exc.VlanIDNotAvailable()
+def reserve_specific_vlanid(vlan_id, net_id):
+ """Reserve a specific vlanid"""
+ LOG.debug("reserve_specific_vlanid() called")
+ 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()
+ try:
+ rvlanid = (session.query(l2network_models.VlanID).
+ filter_by(vlan_id=vlan_id).
+ one())
+ if rvlanid["vlan_used"]:
+ raise q_exc.VlanIdInUse(net_id=net_id, vlan_id=vlan_id)
+ LOG.debug("reserving dynamic vlanid %s" % vlan_id)
+ rvlanid["vlan_used"] = True
+ session.merge(rvlanid)
+ except exc.NoResultFound:
+ rvlanid = l2network_models.VlanID(vlan_id)
+ LOG.debug("reserving non-dynamic vlanid %s" % vlan_id)
+ rvlanid["vlan_used"] = True
+ session.add(rvlanid)
+ session.flush()
+
+
def get_all_vlanids_used():
- """Gets all the vlanids used"""
+ """Get all the vlanids used"""
LOG.debug("get_all_vlanids() called")
session = db.get_session()
try:
def get_all_vlan_bindings():
- """Lists all the vlan to network associations"""
+ """List all the vlan to network associations"""
LOG.debug("get_all_vlan_bindings() called")
session = db.get_session()
try:
def get_vlan_binding(netid):
- """Lists the vlan given a network_id"""
+ """List the vlan given a network_id"""
LOG.debug("get_vlan_binding() called")
session = db.get_session()
try:
def add_vlan_binding(vlanid, netid):
- """Adds a vlan to network association"""
+ """Add a vlan to network association"""
LOG.debug("add_vlan_binding() called")
session = db.get_session()
try:
def remove_vlan_binding(netid):
- """Removes a vlan to network association"""
+ """Remove a vlan to network association"""
LOG.debug("remove_vlan_binding() called")
session = db.get_session()
try:
def update_vlan_binding(netid, newvlanid=None):
- """Updates a vlan to network association"""
+ """Update a vlan to network association"""
LOG.debug("update_vlan_binding() called")
session = db.get_session()
try:
import logging
+from quantum.api.v2 import attributes
from quantum.db import db_base_plugin_v2
from quantum.db import models_v2
from quantum.plugins.linuxbridge.db import l2network_db as cdb
+from quantum import policy
LOG = logging.getLogger(__name__)
class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2):
- """
- LinuxBridgePlugin provides support for Quantum abstractions
- using LinuxBridge. A new VLAN is created for each network.
- It relies on an agent to perform the actual bridge configuration
- on each host.
+ """Implement the Quantum abstractions using Linux bridging.
+
+ A new VLAN is created for each network. An agent is relied upon
+ to perform the actual Linux bridge configuration on each host.
+
+ The provider extension is also supported. As discussed in
+ https://bugs.launchpad.net/quantum/+bug/1023156, this class could
+ be simplified, and filtering on extended attributes could be
+ handled, by adding support for extended attributes to the
+ QuantumDbPluginV2 base class. When that occurs, this class should
+ be updated to take advantage of it.
"""
+ supported_extension_aliases = ["provider"]
+
def __init__(self):
cdb.initialize(base=models_v2.model_base.BASEV2)
LOG.debug("Linux Bridge Plugin initialization complete")
+ # TODO(rkukura) Use core mechanism for attribute authorization
+ # when available.
+
+ def _check_provider_view_auth(self, context, network):
+ return policy.check(context,
+ "extension:provider_network:view",
+ network)
+
+ def _enforce_provider_set_auth(self, context, network):
+ return policy.enforce(context,
+ "extension:provider_network:set",
+ network)
+
+ def _extend_network_dict(self, context, network):
+ if self._check_provider_view_auth(context, network):
+ vlan_binding = cdb.get_vlan_binding(network['id'])
+ network['provider:vlan_id'] = vlan_binding['vlan_id']
+
def create_network(self, context, network):
- new_network = super(LinuxBridgePluginV2, self).create_network(context,
- network)
+ net = super(LinuxBridgePluginV2, self).create_network(context,
+ network)
try:
- vlan_id = cdb.reserve_vlanid()
- cdb.add_vlan_binding(vlan_id, new_network['id'])
+ 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'])
+ else:
+ vlan_id = cdb.reserve_vlanid()
+ cdb.add_vlan_binding(vlan_id, net['id'])
+ self._extend_network_dict(context, net)
except:
super(LinuxBridgePluginV2, self).delete_network(context,
- new_network['id'])
+ net['id'])
raise
- return new_network
+ return net
+
+ def update_network(self, context, id, network):
+ net = super(LinuxBridgePluginV2, self).update_network(context, id,
+ network)
+ self._extend_network_dict(context, net)
+ return net
def delete_network(self, context, id):
vlan_binding = cdb.get_vlan_binding(id)
cdb.release_vlanid(vlan_binding['vlan_id'])
cdb.remove_vlan_binding(id)
return super(LinuxBridgePluginV2, self).delete_network(context, id)
+
+ def get_network(self, context, id, fields=None, verbose=None):
+ net = super(LinuxBridgePluginV2, self).get_network(context, id,
+ None, verbose)
+ self._extend_network_dict(context, net)
+ return self._fields(net, fields)
+
+ def get_networks(self, context, filters=None, fields=None, verbose=None):
+ nets = super(LinuxBridgePluginV2, self).get_networks(context, filters,
+ None, verbose)
+ for net in nets:
+ self._extend_network_dict(context, net)
+ # TODO(rkukura): Filter on extended attributes.
+ return [self._fields(net, fields) for net in nets]
"""
import logging
-import unittest
+import unittest2 as unittest
import quantum.db.api as db
+import quantum.plugins.linuxbridge.common.exceptions as c_exc
import quantum.plugins.linuxbridge.db.l2network_db as l2network_db
"""Tear Down"""
db.clear_db()
- def testa_create_vlanbinding(self):
- """test add vlan binding"""
+ def test_create_vlanbinding(self):
net1 = self.quantum.create_network("t1", "netid1")
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
self.assertTrue(vlan1["vlan-id"] == "10")
self.teardown_vlanbinding()
self.teardown_network()
- def testb_getall_vlanbindings(self):
- """test get all vlan binding"""
+ def test_getall_vlanbindings(self):
net1 = self.quantum.create_network("t1", "netid1")
net2 = self.quantum.create_network("t1", "netid2")
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
self.teardown_vlanbinding()
self.teardown_network()
- def testc_delete_vlanbinding(self):
- """test delete vlan binding"""
+ def test_delete_vlanbinding(self):
net1 = self.quantum.create_network("t1", "netid1")
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
self.assertTrue(vlan1["vlan-id"] == "10")
self.teardown_vlanbinding()
self.teardown_network()
- def testd_update_vlanbinding(self):
- """test update vlan binding"""
+ def test_update_vlanbinding(self):
net1 = self.quantum.create_network("t1", "netid1")
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
self.assertTrue(vlan1["vlan-id"] == "10")
self.teardown_vlanbinding()
self.teardown_network()
- def teste_test_vlanids(self):
- """test vlanid methods"""
+ def test_vlanids(self):
l2network_db.create_vlanids()
vlanids = l2network_db.get_all_vlanids()
- self.assertTrue(len(vlanids) > 0)
+ self.assertGreater(len(vlanids), 0)
vlanid = l2network_db.reserve_vlanid()
used = l2network_db.is_vlanid_used(vlanid)
self.assertTrue(used)
used = l2network_db.release_vlanid(vlanid)
self.assertFalse(used)
- #counting on default teardown here to clear db
+ self.teardown_vlanbinding()
+ self.teardown_network()
+
+ def test_specific_vlanid_outside(self):
+ l2network_db.create_vlanids()
+ orig_count = len(l2network_db.get_all_vlanids())
+ self.assertGreater(orig_count, 0)
+ 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")
+ self.assertTrue(l2network_db.is_vlanid_used(vlan_id))
+ count = len(l2network_db.get_all_vlanids())
+ self.assertEqual(count, orig_count + 1)
+ used = l2network_db.release_vlanid(vlan_id)
+ self.assertFalse(used)
+ with self.assertRaises(c_exc.VlanIDNotFound):
+ l2network_db.is_vlanid_used(vlan_id)
+ count = len(l2network_db.get_all_vlanids())
+ self.assertEqual(count, orig_count)
+ self.teardown_vlanbinding()
+ self.teardown_network()
+
+ def test_specific_vlanid_inside(self):
+ l2network_db.create_vlanids()
+ orig_count = len(l2network_db.get_all_vlanids())
+ 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")
+ self.assertTrue(l2network_db.is_vlanid_used(vlan_id))
+ count = len(l2network_db.get_all_vlanids())
+ self.assertEqual(count, orig_count)
+ used = l2network_db.release_vlanid(vlan_id)
+ self.assertFalse(used)
+ self.assertFalse(l2network_db.is_vlanid_used(vlan_id))
+ count = len(l2network_db.get_all_vlanids())
+ self.assertEqual(count, orig_count)
+ self.teardown_vlanbinding()
+ self.teardown_network()
def teardown_network(self):
"""tearDown Network table"""
return [(binding.vlan_id, binding.network_id) for binding in bindings]
+def get_vlan(net_id):
+ session = db.get_session()
+ try:
+ binding = (session.query(ovs_models_v2.VlanBinding).
+ filter_by(network_id=net_id).
+ one())
+ except exc.NoResultFound:
+ return
+ return binding.vlan_id
+
+
def add_vlan_binding(vlan_id, net_id):
session = db.get_session()
binding = ovs_models_v2.VlanBinding(vlan_id, net_id)
# @author: Dan Wendlandt, Nicira Networks, Inc.
# @author: Dave Lapsley, Nicira Networks, Inc.
# @author: Aaron Rosen, Nicira Networks, Inc.
+# @author: Bob Kukura, Red Hat, Inc.
import logging
import os
from quantum.api.api_common import OperationalStatus
+from quantum.api.v2 import attributes
from quantum.common import exceptions as q_exc
from quantum.common.utils import find_config_file
from quantum.db import api as db
from quantum.plugins.openvswitch import ovs_db
from quantum.plugins.openvswitch import ovs_db_v2
from quantum.quantum_plugin_base import QuantumPluginBase
+from quantum import policy
LOG = logging.getLogger("ovs_quantum_plugin")
raise NoFreeVLANException("No VLAN free for network %s" %
network_id)
+ def acquire_specific(self, vlan_id, network_id):
+ LOG.debug("Allocating specific VLAN %s for network %s"
+ % (vlan_id, network_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)
+ if self.vlans.get(vlan_id):
+ raise q_exc.VlanIdInUse(net_id=network_id,
+ vlan_id=vlan_id)
+ self.free_vlans.discard(vlan_id)
+ self.set_vlan(vlan_id, network_id)
+
def release(self, network_id):
vlan = self.net_ids.get(network_id, None)
if vlan is not None:
- self.free_vlans.add(vlan)
+ if vlan >= self.vlan_min and vlan <= self.vlan_max:
+ self.free_vlans.add(vlan)
del self.vlans[vlan]
del self.net_ids[network_id]
LOG.debug("Deallocated VLAN %s (used by network %s)" %
class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
+ """Implement the Quantum abstractions using Open vSwitch.
+
+ Depending on whether tunneling is enabled, either a GRE tunnel or
+ a new VLAN is created for each network. An agent is relied upon to
+ perform the actual OVS configuration on each host.
+
+ The provider extension is also supported. As discussed in
+ https://bugs.launchpad.net/quantum/+bug/1023156, this class could
+ be simplified, and filtering on extended attributes could be
+ handled, by adding support for extended attributes to the
+ QuantumDbPluginV2 base class. When that occurs, this class should
+ be updated to take advantage of it.
+ """
+
+ supported_extension_aliases = ["provider"]
+
def __init__(self, configfile=None):
conf = config.parse(CONF_FILE)
+ self.enable_tunneling = conf.OVS.enable_tunneling
+
options = {"sql_connection": conf.DATABASE.sql_connection}
options.update({'base': models_v2.model_base.BASEV2})
sql_max_retries = conf.DATABASE.sql_max_retries
self.vmap = VlanMap(conf.OVS.vlan_min, conf.OVS.vlan_max)
self.vmap.populate_already_used(ovs_db_v2.get_vlans())
+ # TODO(rkukura) Use core mechanism for attribute authorization
+ # when available.
+
+ def _check_provider_view_auth(self, context, network):
+ return policy.check(context,
+ "extension:provider_network:view",
+ network)
+
+ def _enforce_provider_set_auth(self, context, network):
+ return policy.enforce(context,
+ "extension:provider_network:set",
+ network)
+
+ def _extend_network_dict(self, context, network):
+ if self._check_provider_view_auth(context, network):
+ if not self.enable_tunneling:
+ network['provider:vlan_id'] = ovs_db_v2.get_vlan(network['id'])
+
def create_network(self, context, network):
net = super(OVSQuantumPluginV2, self).create_network(context, network)
try:
- vlan_id = self.vmap.acquire(str(net['id']))
- except NoFreeVLANException:
+ 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']))
+ else:
+ vlan_id = self.vmap.acquire(str(net['id']))
+ except Exception:
super(OVSQuantumPluginV2, self).delete_network(context, net['id'])
raise
LOG.debug("Created network: %s" % net['id'])
ovs_db_v2.add_vlan_binding(vlan_id, str(net['id']))
+ self._extend_network_dict(context, net)
+ return net
+
+ def update_network(self, context, id, network):
+ net = super(OVSQuantumPluginV2, self).update_network(context, id,
+ network)
+ self._extend_network_dict(context, net)
return net
def delete_network(self, context, id):
ovs_db_v2.remove_vlan_binding(id)
self.vmap.release(id)
return super(OVSQuantumPluginV2, self).delete_network(context, id)
+
+ def get_network(self, context, id, fields=None, verbose=None):
+ net = super(OVSQuantumPluginV2, self).get_network(context, id,
+ None, verbose)
+ self._extend_network_dict(context, net)
+ return self._fields(net, fields)
+
+ def get_networks(self, context, filters=None, fields=None, verbose=None):
+ nets = super(OVSQuantumPluginV2, self).get_networks(context, filters,
+ None, verbose)
+ for net in nets:
+ self._extend_network_dict(context, net)
+ # TODO(rkukura): Filter on extended attributes.
+ return [self._fields(net, fields) for net in nets]