]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Initial V2 implementation of provider extension.
authorBob Kukura <rkukura@redhat.com>
Fri, 15 Jun 2012 14:20:05 +0000 (10:20 -0400)
committerBob Kukura <rkukura@redhat.com>
Tue, 24 Jul 2012 01:49:01 +0000 (21:49 -0400)
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

etc/policy.json
quantum/api/v2/base.py
quantum/common/exceptions.py
quantum/extensions/providernet.py [new file with mode: 0644]
quantum/plugins/linuxbridge/db/l2network_db.py
quantum/plugins/linuxbridge/lb_quantum_plugin.py
quantum/plugins/linuxbridge/tests/unit/test_database.py
quantum/plugins/openvswitch/ovs_db_v2.py
quantum/plugins/openvswitch/ovs_quantum_plugin.py

index 41a5cafbe559af6e090dc7460725b2eda337a0f8..1fcc3306ebe97f18e0e763962f229c32f1d6ace8 100644 (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"]],
index 64786d34221ad5cedb3ad0a316836a0624535904..c093a8e9e5b6682102c031f52ba7aa02d6296b85 100644 (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
index 88e548927adc121b39c410b0d6597632d1beacd5..04595c61cead93b233f6191f1d5493b2ce3526e1 100644 (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 "
diff --git a/quantum/extensions/providernet.py b/quantum/extensions/providernet.py
new file mode 100644 (file)
index 0000000..6ed2c29
--- /dev/null
@@ -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 {}
index 8cd4014bda3282b5e655bc31c0dd9ec92ca0b44a..b2e87bf709568b011e846be2026804531a0fd1db 100644 (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:
index ffb52ba7173ccb986a3bd96ac807a18b7c929fd6..573c7368e18c86997d108e588b61aee896ae9dc6 100644 (file)
 
 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]
index ee9c8fe64f73d467d0f9efdd43db88d405ec5cc4..230315390dc37bddd64263d1957a70d33223ae22 100644 (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"""
index 9cdaff66665c00d923cd40f566aee7b731f491f7..bab6d4f3a24779acc7014d7bb3f50be06b525cc9 100644 (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)
index 13d0a45d9b467cfcba2bed197c0f0d5b643f3604..7ae993015106f3aa51026f0f336fa18cef8bbefd 100644 (file)
 # @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]