Initial V2 implementation of provider extension.
Initial provider extension implementation. Specify vlan_id using the CLI with admin rights via "net-create --tenant_id <tenant-id> <net-name> --provider:vlan_id <vlan-id>". Also includes provider:vlan_id in reply messages for admins. The extension is supported in the linuxbridge and openvswitch plugins. Partially implements blueprint provider-networks. Change-Id: I2fff64c4247b1a3091c28c7a2cd632afda192c3d
This commit is contained in:
parent
4ff0723479
commit
8c4bf35267
@ -2,6 +2,10 @@
|
|||||||
"admin_or_owner": [["role:admin"], ["tenant_id:%(tenant_id)s"]],
|
"admin_or_owner": [["role:admin"], ["tenant_id:%(tenant_id)s"]],
|
||||||
"default": [["rule:admin_or_owner"]],
|
"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": [],
|
"create_subnet": [],
|
||||||
"get_subnet": [["rule:admin_or_owner"]],
|
"get_subnet": [["rule:admin_or_owner"]],
|
||||||
"update_subnet": [["rule:admin_or_owner"]],
|
"update_subnet": [["rule:admin_or_owner"]],
|
||||||
|
@ -346,7 +346,8 @@ class Controller(object):
|
|||||||
for attr, attr_vals in self._attr_info.iteritems():
|
for attr, attr_vals in self._attr_info.iteritems():
|
||||||
# Convert values if necessary
|
# Convert values if necessary
|
||||||
if ('convert_to' in attr_vals and
|
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])
|
res_dict[attr] = attr_vals['convert_to'](res_dict[attr])
|
||||||
|
|
||||||
# Check that configured values are correct
|
# Check that configured values are correct
|
||||||
|
@ -105,6 +105,11 @@ class IpAddressInUse(InUse):
|
|||||||
"The IP address %(ip_address)s is in use.")
|
"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):
|
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 "
|
||||||
|
66
quantum/extensions/providernet.py
Normal file
66
quantum/extensions/providernet.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# 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 {}
|
@ -50,7 +50,7 @@ def initialize(base=None):
|
|||||||
|
|
||||||
|
|
||||||
def create_vlanids():
|
def create_vlanids():
|
||||||
"""Prepopulates the vlan_bindings table"""
|
"""Prepopulate the vlan_bindings table"""
|
||||||
LOG.debug("create_vlanids() called")
|
LOG.debug("create_vlanids() called")
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
start = CONF.VLANS.vlan_start
|
start = CONF.VLANS.vlan_start
|
||||||
@ -87,7 +87,7 @@ def create_vlanids():
|
|||||||
|
|
||||||
|
|
||||||
def get_all_vlanids():
|
def get_all_vlanids():
|
||||||
"""Gets all the vlanids"""
|
"""Get all the vlanids"""
|
||||||
LOG.debug("get_all_vlanids() called")
|
LOG.debug("get_all_vlanids() called")
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
try:
|
try:
|
||||||
@ -99,7 +99,7 @@ def get_all_vlanids():
|
|||||||
|
|
||||||
|
|
||||||
def is_vlanid_used(vlan_id):
|
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")
|
LOG.debug("is_vlanid_used() called")
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
try:
|
try:
|
||||||
@ -112,7 +112,7 @@ def is_vlanid_used(vlan_id):
|
|||||||
|
|
||||||
|
|
||||||
def release_vlanid(vlan_id):
|
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")
|
LOG.debug("release_vlanid() called")
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
try:
|
try:
|
||||||
@ -120,7 +120,10 @@ def release_vlanid(vlan_id):
|
|||||||
filter_by(vlan_id=vlan_id).
|
filter_by(vlan_id=vlan_id).
|
||||||
one())
|
one())
|
||||||
vlanid["vlan_used"] = False
|
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()
|
session.flush()
|
||||||
return vlanid["vlan_used"]
|
return vlanid["vlan_used"]
|
||||||
except exc.NoResultFound:
|
except exc.NoResultFound:
|
||||||
@ -129,7 +132,7 @@ def release_vlanid(vlan_id):
|
|||||||
|
|
||||||
|
|
||||||
def delete_vlanid(vlan_id):
|
def delete_vlanid(vlan_id):
|
||||||
"""Deletes a vlanid entry from db"""
|
"""Delete a vlanid entry from db"""
|
||||||
LOG.debug("delete_vlanid() called")
|
LOG.debug("delete_vlanid() called")
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
try:
|
try:
|
||||||
@ -144,7 +147,7 @@ def delete_vlanid(vlan_id):
|
|||||||
|
|
||||||
|
|
||||||
def reserve_vlanid():
|
def reserve_vlanid():
|
||||||
"""Reserves the first unused vlanid"""
|
"""Reserve the first unused vlanid"""
|
||||||
LOG.debug("reserve_vlanid() called")
|
LOG.debug("reserve_vlanid() called")
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
try:
|
try:
|
||||||
@ -170,8 +173,32 @@ def reserve_vlanid():
|
|||||||
raise c_exc.VlanIDNotAvailable()
|
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():
|
def get_all_vlanids_used():
|
||||||
"""Gets all the vlanids used"""
|
"""Get all the vlanids used"""
|
||||||
LOG.debug("get_all_vlanids() called")
|
LOG.debug("get_all_vlanids() called")
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
try:
|
try:
|
||||||
@ -184,7 +211,7 @@ def get_all_vlanids_used():
|
|||||||
|
|
||||||
|
|
||||||
def get_all_vlan_bindings():
|
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")
|
LOG.debug("get_all_vlan_bindings() called")
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
try:
|
try:
|
||||||
@ -196,7 +223,7 @@ def get_all_vlan_bindings():
|
|||||||
|
|
||||||
|
|
||||||
def get_vlan_binding(netid):
|
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")
|
LOG.debug("get_vlan_binding() called")
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
try:
|
try:
|
||||||
@ -209,7 +236,7 @@ def get_vlan_binding(netid):
|
|||||||
|
|
||||||
|
|
||||||
def add_vlan_binding(vlanid, netid):
|
def add_vlan_binding(vlanid, netid):
|
||||||
"""Adds a vlan to network association"""
|
"""Add a vlan to network association"""
|
||||||
LOG.debug("add_vlan_binding() called")
|
LOG.debug("add_vlan_binding() called")
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
try:
|
try:
|
||||||
@ -226,7 +253,7 @@ def add_vlan_binding(vlanid, netid):
|
|||||||
|
|
||||||
|
|
||||||
def remove_vlan_binding(netid):
|
def remove_vlan_binding(netid):
|
||||||
"""Removes a vlan to network association"""
|
"""Remove a vlan to network association"""
|
||||||
LOG.debug("remove_vlan_binding() called")
|
LOG.debug("remove_vlan_binding() called")
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
try:
|
try:
|
||||||
@ -241,7 +268,7 @@ def remove_vlan_binding(netid):
|
|||||||
|
|
||||||
|
|
||||||
def update_vlan_binding(netid, newvlanid=None):
|
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")
|
LOG.debug("update_vlan_binding() called")
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
try:
|
try:
|
||||||
|
@ -15,40 +15,94 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from quantum.api.v2 import attributes
|
||||||
from quantum.db import db_base_plugin_v2
|
from quantum.db import db_base_plugin_v2
|
||||||
from quantum.db import models_v2
|
from quantum.db import models_v2
|
||||||
from quantum.plugins.linuxbridge.db import l2network_db as cdb
|
from quantum.plugins.linuxbridge.db import l2network_db as cdb
|
||||||
|
from quantum import policy
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
||||||
|
"""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.
|
||||||
"""
|
"""
|
||||||
LinuxBridgePlugin provides support for Quantum abstractions
|
|
||||||
using LinuxBridge. A new VLAN is created for each network.
|
supported_extension_aliases = ["provider"]
|
||||||
It relies on an agent to perform the actual bridge configuration
|
|
||||||
on each host.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
cdb.initialize(base=models_v2.model_base.BASEV2)
|
cdb.initialize(base=models_v2.model_base.BASEV2)
|
||||||
LOG.debug("Linux Bridge Plugin initialization complete")
|
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):
|
def create_network(self, context, network):
|
||||||
new_network = super(LinuxBridgePluginV2, self).create_network(context,
|
net = super(LinuxBridgePluginV2, self).create_network(context,
|
||||||
network)
|
network)
|
||||||
try:
|
try:
|
||||||
vlan_id = cdb.reserve_vlanid()
|
vlan_id = network['network'].get('provider:vlan_id')
|
||||||
cdb.add_vlan_binding(vlan_id, new_network['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:
|
except:
|
||||||
super(LinuxBridgePluginV2, self).delete_network(context,
|
super(LinuxBridgePluginV2, self).delete_network(context,
|
||||||
new_network['id'])
|
net['id'])
|
||||||
raise
|
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):
|
def delete_network(self, context, id):
|
||||||
vlan_binding = cdb.get_vlan_binding(id)
|
vlan_binding = cdb.get_vlan_binding(id)
|
||||||
cdb.release_vlanid(vlan_binding['vlan_id'])
|
cdb.release_vlanid(vlan_binding['vlan_id'])
|
||||||
cdb.remove_vlan_binding(id)
|
cdb.remove_vlan_binding(id)
|
||||||
return super(LinuxBridgePluginV2, self).delete_network(context, 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]
|
||||||
|
@ -21,9 +21,10 @@ that tests the database api method calls
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import unittest
|
import unittest2 as unittest
|
||||||
|
|
||||||
import quantum.db.api as db
|
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
|
import quantum.plugins.linuxbridge.db.l2network_db as l2network_db
|
||||||
|
|
||||||
|
|
||||||
@ -157,16 +158,14 @@ class L2networkDBTest(unittest.TestCase):
|
|||||||
"""Tear Down"""
|
"""Tear Down"""
|
||||||
db.clear_db()
|
db.clear_db()
|
||||||
|
|
||||||
def testa_create_vlanbinding(self):
|
def test_create_vlanbinding(self):
|
||||||
"""test add vlan binding"""
|
|
||||||
net1 = self.quantum.create_network("t1", "netid1")
|
net1 = self.quantum.create_network("t1", "netid1")
|
||||||
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
|
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
|
||||||
self.assertTrue(vlan1["vlan-id"] == "10")
|
self.assertTrue(vlan1["vlan-id"] == "10")
|
||||||
self.teardown_vlanbinding()
|
self.teardown_vlanbinding()
|
||||||
self.teardown_network()
|
self.teardown_network()
|
||||||
|
|
||||||
def testb_getall_vlanbindings(self):
|
def test_getall_vlanbindings(self):
|
||||||
"""test get all vlan binding"""
|
|
||||||
net1 = self.quantum.create_network("t1", "netid1")
|
net1 = self.quantum.create_network("t1", "netid1")
|
||||||
net2 = self.quantum.create_network("t1", "netid2")
|
net2 = self.quantum.create_network("t1", "netid2")
|
||||||
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
|
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
|
||||||
@ -178,8 +177,7 @@ class L2networkDBTest(unittest.TestCase):
|
|||||||
self.teardown_vlanbinding()
|
self.teardown_vlanbinding()
|
||||||
self.teardown_network()
|
self.teardown_network()
|
||||||
|
|
||||||
def testc_delete_vlanbinding(self):
|
def test_delete_vlanbinding(self):
|
||||||
"""test delete vlan binding"""
|
|
||||||
net1 = self.quantum.create_network("t1", "netid1")
|
net1 = self.quantum.create_network("t1", "netid1")
|
||||||
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
|
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
|
||||||
self.assertTrue(vlan1["vlan-id"] == "10")
|
self.assertTrue(vlan1["vlan-id"] == "10")
|
||||||
@ -193,8 +191,7 @@ class L2networkDBTest(unittest.TestCase):
|
|||||||
self.teardown_vlanbinding()
|
self.teardown_vlanbinding()
|
||||||
self.teardown_network()
|
self.teardown_network()
|
||||||
|
|
||||||
def testd_update_vlanbinding(self):
|
def test_update_vlanbinding(self):
|
||||||
"""test update vlan binding"""
|
|
||||||
net1 = self.quantum.create_network("t1", "netid1")
|
net1 = self.quantum.create_network("t1", "netid1")
|
||||||
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
|
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
|
||||||
self.assertTrue(vlan1["vlan-id"] == "10")
|
self.assertTrue(vlan1["vlan-id"] == "10")
|
||||||
@ -203,17 +200,55 @@ class L2networkDBTest(unittest.TestCase):
|
|||||||
self.teardown_vlanbinding()
|
self.teardown_vlanbinding()
|
||||||
self.teardown_network()
|
self.teardown_network()
|
||||||
|
|
||||||
def teste_test_vlanids(self):
|
def test_vlanids(self):
|
||||||
"""test vlanid methods"""
|
|
||||||
l2network_db.create_vlanids()
|
l2network_db.create_vlanids()
|
||||||
vlanids = l2network_db.get_all_vlanids()
|
vlanids = l2network_db.get_all_vlanids()
|
||||||
self.assertTrue(len(vlanids) > 0)
|
self.assertGreater(len(vlanids), 0)
|
||||||
vlanid = l2network_db.reserve_vlanid()
|
vlanid = l2network_db.reserve_vlanid()
|
||||||
used = l2network_db.is_vlanid_used(vlanid)
|
used = l2network_db.is_vlanid_used(vlanid)
|
||||||
self.assertTrue(used)
|
self.assertTrue(used)
|
||||||
used = l2network_db.release_vlanid(vlanid)
|
used = l2network_db.release_vlanid(vlanid)
|
||||||
self.assertFalse(used)
|
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):
|
def teardown_network(self):
|
||||||
"""tearDown Network table"""
|
"""tearDown Network table"""
|
||||||
|
@ -31,6 +31,17 @@ def get_vlans():
|
|||||||
return [(binding.vlan_id, binding.network_id) for binding in bindings]
|
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):
|
def add_vlan_binding(vlan_id, net_id):
|
||||||
session = db.get_session()
|
session = db.get_session()
|
||||||
binding = ovs_models_v2.VlanBinding(vlan_id, net_id)
|
binding = ovs_models_v2.VlanBinding(vlan_id, net_id)
|
||||||
|
@ -18,11 +18,13 @@
|
|||||||
# @author: Dan Wendlandt, Nicira Networks, Inc.
|
# @author: Dan Wendlandt, Nicira Networks, Inc.
|
||||||
# @author: Dave Lapsley, Nicira Networks, Inc.
|
# @author: Dave Lapsley, Nicira Networks, Inc.
|
||||||
# @author: Aaron Rosen, Nicira Networks, Inc.
|
# @author: Aaron Rosen, Nicira Networks, Inc.
|
||||||
|
# @author: Bob Kukura, Red Hat, Inc.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from quantum.api.api_common import OperationalStatus
|
from quantum.api.api_common import OperationalStatus
|
||||||
|
from quantum.api.v2 import attributes
|
||||||
from quantum.common import exceptions as q_exc
|
from quantum.common import exceptions as q_exc
|
||||||
from quantum.common.utils import find_config_file
|
from quantum.common.utils import find_config_file
|
||||||
from quantum.db import api as db
|
from quantum.db import api as db
|
||||||
@ -32,6 +34,7 @@ from quantum.plugins.openvswitch.common import config
|
|||||||
from quantum.plugins.openvswitch import ovs_db
|
from quantum.plugins.openvswitch import ovs_db
|
||||||
from quantum.plugins.openvswitch import ovs_db_v2
|
from quantum.plugins.openvswitch import ovs_db_v2
|
||||||
from quantum.quantum_plugin_base import QuantumPluginBase
|
from quantum.quantum_plugin_base import QuantumPluginBase
|
||||||
|
from quantum import policy
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("ovs_quantum_plugin")
|
LOG = logging.getLogger("ovs_quantum_plugin")
|
||||||
@ -80,10 +83,23 @@ class VlanMap(object):
|
|||||||
raise NoFreeVLANException("No VLAN free for network %s" %
|
raise NoFreeVLANException("No VLAN free for network %s" %
|
||||||
network_id)
|
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):
|
def release(self, network_id):
|
||||||
vlan = self.net_ids.get(network_id, None)
|
vlan = self.net_ids.get(network_id, None)
|
||||||
if vlan is not 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.vlans[vlan]
|
||||||
del self.net_ids[network_id]
|
del self.net_ids[network_id]
|
||||||
LOG.debug("Deallocated VLAN %s (used by network %s)" %
|
LOG.debug("Deallocated VLAN %s (used by network %s)" %
|
||||||
@ -234,8 +250,26 @@ class OVSQuantumPlugin(QuantumPluginBase):
|
|||||||
|
|
||||||
|
|
||||||
class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
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):
|
def __init__(self, configfile=None):
|
||||||
conf = config.parse(CONF_FILE)
|
conf = config.parse(CONF_FILE)
|
||||||
|
self.enable_tunneling = conf.OVS.enable_tunneling
|
||||||
|
|
||||||
options = {"sql_connection": conf.DATABASE.sql_connection}
|
options = {"sql_connection": conf.DATABASE.sql_connection}
|
||||||
options.update({'base': models_v2.model_base.BASEV2})
|
options.update({'base': models_v2.model_base.BASEV2})
|
||||||
sql_max_retries = conf.DATABASE.sql_max_retries
|
sql_max_retries = conf.DATABASE.sql_max_retries
|
||||||
@ -247,19 +281,63 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
|||||||
self.vmap = VlanMap(conf.OVS.vlan_min, conf.OVS.vlan_max)
|
self.vmap = VlanMap(conf.OVS.vlan_min, conf.OVS.vlan_max)
|
||||||
self.vmap.populate_already_used(ovs_db_v2.get_vlans())
|
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):
|
def create_network(self, context, network):
|
||||||
net = super(OVSQuantumPluginV2, self).create_network(context, network)
|
net = super(OVSQuantumPluginV2, self).create_network(context, network)
|
||||||
try:
|
try:
|
||||||
vlan_id = self.vmap.acquire(str(net['id']))
|
vlan_id = network['network'].get('provider:vlan_id')
|
||||||
except NoFreeVLANException:
|
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'])
|
super(OVSQuantumPluginV2, self).delete_network(context, net['id'])
|
||||||
raise
|
raise
|
||||||
|
|
||||||
LOG.debug("Created network: %s" % net['id'])
|
LOG.debug("Created network: %s" % net['id'])
|
||||||
ovs_db_v2.add_vlan_binding(vlan_id, str(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
|
return net
|
||||||
|
|
||||||
def delete_network(self, context, id):
|
def delete_network(self, context, id):
|
||||||
ovs_db_v2.remove_vlan_binding(id)
|
ovs_db_v2.remove_vlan_binding(id)
|
||||||
self.vmap.release(id)
|
self.vmap.release(id)
|
||||||
return super(OVSQuantumPluginV2, self).delete_network(context, 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]
|
||||||
|
Loading…
Reference in New Issue
Block a user