Merge "Initial V2 implementation of provider extension."

This commit is contained in:
Jenkins 2012-07-24 21:45:53 +00:00 committed by Gerrit Code Review
commit 50c770ef01
9 changed files with 322 additions and 41 deletions

View File

@ -2,6 +2,10 @@
"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"]],

View File

@ -346,7 +346,8 @@ class Controller(object):
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

View File

@ -105,6 +105,11 @@ class IpAddressInUse(InUse):
"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 "

View 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 {}

View File

@ -50,7 +50,7 @@ def initialize(base=None):
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
@ -87,7 +87,7 @@ def create_vlanids():
def get_all_vlanids():
"""Gets all the vlanids"""
"""Get all the vlanids"""
LOG.debug("get_all_vlanids() called")
session = db.get_session()
try:
@ -99,7 +99,7 @@ def get_all_vlanids():
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:
@ -112,7 +112,7 @@ def is_vlanid_used(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")
session = db.get_session()
try:
@ -120,7 +120,10 @@ def release_vlanid(vlan_id):
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:
@ -129,7 +132,7 @@ def release_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")
session = db.get_session()
try:
@ -144,7 +147,7 @@ def delete_vlanid(vlan_id):
def reserve_vlanid():
"""Reserves the first unused vlanid"""
"""Reserve the first unused vlanid"""
LOG.debug("reserve_vlanid() called")
session = db.get_session()
try:
@ -170,8 +173,32 @@ def reserve_vlanid():
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:
@ -184,7 +211,7 @@ def get_all_vlanids_used():
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:
@ -196,7 +223,7 @@ def get_all_vlan_bindings():
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:
@ -209,7 +236,7 @@ def get_vlan_binding(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")
session = db.get_session()
try:
@ -226,7 +253,7 @@ def add_vlan_binding(vlanid, netid):
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:
@ -241,7 +268,7 @@ def remove_vlan_binding(netid):
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:

View File

@ -15,40 +15,94 @@
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):
"""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.
It relies on an agent to perform the actual bridge configuration
on each host.
"""
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]

View File

@ -21,9 +21,10 @@ that tests the database api method calls
"""
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
@ -157,16 +158,14 @@ class L2networkDBTest(unittest.TestCase):
"""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"])
@ -178,8 +177,7 @@ class L2networkDBTest(unittest.TestCase):
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")
@ -193,8 +191,7 @@ class L2networkDBTest(unittest.TestCase):
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")
@ -203,17 +200,55 @@ class L2networkDBTest(unittest.TestCase):
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"""

View File

@ -31,6 +31,17 @@ def get_vlans():
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)

View File

@ -18,11 +18,13 @@
# @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
@ -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_v2
from quantum.quantum_plugin_base import QuantumPluginBase
from quantum import policy
LOG = logging.getLogger("ovs_quantum_plugin")
@ -80,10 +83,23 @@ class VlanMap(object):
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)" %
@ -234,8 +250,26 @@ class OVSQuantumPlugin(QuantumPluginBase):
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
@ -247,19 +281,63 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
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]