]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
ML2 Type Driver refactor part 2
authorArvind Somya <asomya@cisco.com>
Mon, 18 Aug 2014 15:59:44 +0000 (08:59 -0700)
committerArvind Somya <asomya@cisco.com>
Sun, 31 Aug 2014 20:30:54 +0000 (13:30 -0700)
This commit builds on top of part 1 to introduce support for creating
dynamic network segments in ML2.

Change-Id: I399e1569baae6f24054aac15c4c51a2e44a20e5b
Partially implements: Blueprint ml2-type-driver-refactor

neutron/db/migration/alembic_migrations/versions/236b90af57ab_ml2_refactor_for_dynamic_segments.py [new file with mode: 0644]
neutron/db/migration/alembic_migrations/versions/HEAD
neutron/plugins/ml2/db.py
neutron/plugins/ml2/driver_api.py
neutron/plugins/ml2/driver_context.py
neutron/plugins/ml2/managers.py
neutron/plugins/ml2/models.py
neutron/plugins/ml2/plugin.py
neutron/tests/unit/ml2/_test_mech_agent.py
neutron/tests/unit/ml2/test_ml2_plugin.py

diff --git a/neutron/db/migration/alembic_migrations/versions/236b90af57ab_ml2_refactor_for_dynamic_segments.py b/neutron/db/migration/alembic_migrations/versions/236b90af57ab_ml2_refactor_for_dynamic_segments.py
new file mode 100644 (file)
index 0000000..4a4db3d
--- /dev/null
@@ -0,0 +1,41 @@
+# Copyright 2014 OpenStack Foundation
+#
+#    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.
+#
+
+"""ml2_type_driver_refactor_dynamic_segments
+
+Revision ID: 236b90af57ab
+Revises: 58fe87a01143
+Create Date: 2014-08-14 16:22:14.293788
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '236b90af57ab'
+down_revision = '58fe87a01143'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade(active_plugins=None, options=None):
+
+    op.add_column('ml2_network_segments',
+                  sa.Column('is_dynamic', sa.Boolean(), nullable=False,
+                            server_default=sa.sql.false()))
+
+
+def downgrade(active_plugins=None, options=None):
+
+    op.drop_column('ml2_network_segments', 'is_dynamic')
index 5c763b0d27b05e9ec7626f5b10bb24b02a5909dd..dbff3d53e07ff422e332dc0e218cd9bce39223fb 100644 (file)
@@ -1 +1 @@
-58fe87a01143
+236b90af57ab
index 242b19ae9621cbf93fac3ce9c9f8412ec1947010..d8caa9384af1bb8567a20e1421adca9df3be3c3e 100644 (file)
@@ -31,16 +31,26 @@ from neutron.plugins.ml2 import models
 LOG = log.getLogger(__name__)
 
 
-def add_network_segment(session, network_id, segment):
+def _make_segment_dict(record):
+    """Make a segment dictionary out of a DB record."""
+    return {api.ID: record.id,
+            api.NETWORK_TYPE: record.network_type,
+            api.PHYSICAL_NETWORK: record.physical_network,
+            api.SEGMENTATION_ID: record.segmentation_id}
+
+
+def add_network_segment(session, network_id, segment, is_dynamic=False):
     with session.begin(subtransactions=True):
         record = models.NetworkSegment(
             id=uuidutils.generate_uuid(),
             network_id=network_id,
             network_type=segment.get(api.NETWORK_TYPE),
             physical_network=segment.get(api.PHYSICAL_NETWORK),
-            segmentation_id=segment.get(api.SEGMENTATION_ID)
+            segmentation_id=segment.get(api.SEGMENTATION_ID),
+            is_dynamic=is_dynamic
         )
         session.add(record)
+        segment[api.ID] = record.id
     LOG.info(_("Added segment %(id)s of type %(network_type)s for network"
                " %(network_id)s"),
              {'id': record.id,
@@ -48,15 +58,58 @@ def add_network_segment(session, network_id, segment):
               'network_id': record.network_id})
 
 
-def get_network_segments(session, network_id):
+def get_network_segments(session, network_id, filter_dynamic=False):
+    with session.begin(subtransactions=True):
+        query = (session.query(models.NetworkSegment).
+                 filter_by(network_id=network_id))
+        if filter_dynamic is not None:
+            query = query.filter_by(is_dynamic=filter_dynamic)
+        records = query.all()
+
+        return [_make_segment_dict(record) for record in records]
+
+
+def get_segment_by_id(session, segment_id):
+    with session.begin(subtransactions=True):
+        try:
+            record = (session.query(models.NetworkSegment).
+                      filter_by(id=segment_id).
+                      one())
+            return _make_segment_dict(record)
+        except exc.NoResultFound:
+            return
+
+
+def get_dynamic_segment(session, network_id, physical_network=None,
+                        segmentation_id=None):
+        """Return a dynamic segment for the filters provided if one exists."""
+        with session.begin(subtransactions=True):
+            query = (session.query(models.NetworkSegment).
+                     filter_by(network_id=network_id, is_dynamic=True))
+            if physical_network:
+                query = query.filter_by(physical_network=physical_network)
+            if segmentation_id:
+                query = query.filter_by(segmentation_id=segmentation_id)
+            record = query.first()
+
+        if record:
+            return _make_segment_dict(record)
+        else:
+            LOG.debug("No dynamic segment %s found for "
+                      "Network:%(network_id)s, "
+                      "Physical network:%(physnet)s, "
+                      "segmentation_id:%(segmentation_id)s",
+                      {'network_id': network_id,
+                       'physnet': physical_network,
+                       'segmentation_id': segmentation_id})
+            return None
+
+
+def delete_network_segment(session, segment_id):
+    """Release a dynamic segment for the params provided if one exists."""
     with session.begin(subtransactions=True):
-        records = (session.query(models.NetworkSegment).
-                   filter_by(network_id=network_id))
-        return [{api.ID: record.id,
-                 api.NETWORK_TYPE: record.network_type,
-                 api.PHYSICAL_NETWORK: record.physical_network,
-                 api.SEGMENTATION_ID: record.segmentation_id}
-                for record in records]
+        (session.query(models.NetworkSegment).
+         filter_by(id=segment_id).delete())
 
 
 def add_port_binding(session, port_id):
index 1fe5f8381fb458b2c0086dd05aa01c9561dececf..cc68181c88745cdf0e754919834946aad433d47a 100644 (file)
@@ -328,6 +328,31 @@ class PortContext(object):
         """
         pass
 
+    @abc.abstractmethod
+    def allocate_dynamic_segment(self, segment):
+        """Allocate a dynamic segment.
+
+        :param segment: A partially or fully specified segment dictionary
+
+        Called by the MechanismDriver.bind_port, create_port or update_port
+        to dynamically allocate a segment for the port using the partial
+        segment specified. The segment dictionary can be a fully or partially
+        specified segment. At a minumim it needs the network_type populated to
+        call on the appropriate type driver.
+        """
+        pass
+
+    @abc.abstractmethod
+    def release_dynamic_segment(self, segment_id):
+        """Release an allocated dynamic segment.
+
+        :param segment_id: UUID of the dynamic network segment.
+
+        Called by the MechanismDriver.delete_port or update_port to release
+        the dynamic segment allocated for this port.
+        """
+        pass
+
 
 @six.add_metaclass(abc.ABCMeta)
 class MechanismDriver(object):
index 2cbc9b56110defe2b47e567ca8db072acdac0889..97987a002add4986fcf5ec94092efe7a983f7549 100644 (file)
@@ -151,6 +151,16 @@ class PortContext(MechanismDriverContext, api.PortContext):
         self._binding.vif_details = jsonutils.dumps(vif_details)
         self._new_port_status = status
 
+    def allocate_dynamic_segment(self, segment):
+        network_id = self._network_context.current['id']
+
+        return self._plugin.type_manager.allocate_dynamic_segment(
+                self._plugin_context.session, network_id, segment)
+
+    def release_dynamic_segment(self, segment_id):
+        return self._plugin.type_manager.release_dynamic_segment(
+                self._plugin_context.session, segment_id)
+
 
 class DvrPortContext(PortContext):
 
index 084829e583711c49dfe8378aafe3b07020bd3c59..9c6c6fc514e026c52cdec9cf809a56253f4992d4 100644 (file)
@@ -191,20 +191,47 @@ class TypeManager(stevedore.named.NamedExtensionManager):
                 return segment
         raise exc.NoNetworkAvailable()
 
-    def release_segment(self, session, segment):
-        network_type = segment.get(api.NETWORK_TYPE)
-        driver = self.drivers.get(network_type)
-        # ML2 may have been reconfigured since the segment was created,
-        # so a driver may no longer exist for this network_type.
-        # REVISIT: network_type-specific db entries may become orphaned
-        # if a network is deleted and the driver isn't available to release
-        # the segment. This may be fixed with explicit foreign-key references
-        # or consistency checks on driver initialization.
-        if not driver:
-            LOG.error(_("Failed to release segment '%s' because "
-                        "network type is not supported."), segment)
-            return
-        driver.obj.release_segment(session, segment)
+    def release_network_segments(self, session, network_id):
+        segments = db.get_network_segments(session, network_id,
+                                           filter_dynamic=None)
+
+        for segment in segments:
+            network_type = segment.get(api.NETWORK_TYPE)
+            driver = self.drivers.get(network_type)
+            if driver:
+                driver.obj.release_segment(session, segment)
+            else:
+                LOG.error(_("Failed to release segment '%s' because "
+                            "network type is not supported."), segment)
+
+    def allocate_dynamic_segment(self, session, network_id, segment):
+        """Allocate a dynamic segment using a partial or full segment dict."""
+        dynamic_segment = db.get_dynamic_segment(
+            session, network_id, segment.get(api.PHYSICAL_NETWORK),
+            segment.get(api.SEGMENTATION_ID))
+
+        if dynamic_segment:
+            return dynamic_segment
+
+        driver = self.drivers.get(segment.get(api.NETWORK_TYPE))
+        dynamic_segment = driver.obj.reserve_provider_segment(session, segment)
+        db.add_network_segment(session, network_id, dynamic_segment,
+                               is_dynamic=True)
+        return dynamic_segment
+
+    def release_dynamic_segment(self, session, segment_id):
+        """Delete a dynamic segment."""
+        segment = db.get_segment_by_id(session, segment_id)
+        if segment:
+            driver = self.drivers.get(segment.get(api.NETWORK_TYPE))
+            if driver:
+                driver.obj.release_segment(session, segment)
+                db.delete_network_segment(session, segment_id)
+            else:
+                LOG.error(_("Failed to release segment '%s' because "
+                            "network type is not supported."), segment)
+        else:
+            LOG.debug("No segment found with id %(segment_id)s", segment_id)
 
 
 class MechanismManager(stevedore.named.NamedExtensionManager):
index 9903017236a79c68d2529e9596a7ffb97d4843dd..80ae1bbb6a33349f717f3465c1aaaacb9a982ecd 100644 (file)
@@ -39,6 +39,8 @@ class NetworkSegment(model_base.BASEV2, models_v2.HasId):
     network_type = sa.Column(sa.String(32), nullable=False)
     physical_network = sa.Column(sa.String(64))
     segmentation_id = sa.Column(sa.Integer)
+    is_dynamic = sa.Column(sa.Boolean, default=False, nullable=False,
+                           server_default=sa.sql.false())
 
 
 class PortBinding(model_base.BASEV2):
index f25c5124104d1f8159245374c03ae6b6e13a0f08..85038b682a4e4935e7946caf358aa47048d03bf4 100644 (file)
@@ -615,9 +615,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
                         record = self._get_network(context, id)
                         LOG.debug(_("Deleting network record %s"), record)
                         session.delete(record)
-
-                        for segment in mech_context.network_segments:
-                            self.type_manager.release_segment(session, segment)
+                        self.type_manager.release_network_segments(session, id)
 
                         # The segment records are deleted via cascade from the
                         # network record, so explicit removal is not necessary.
index 89f5394f3c4e8954c96bd4c7b93902d0cd69d5fe..a42eca0c2acbb8c05af8d7f781c90a2d33a76a13 100644 (file)
@@ -109,6 +109,12 @@ class FakePortContext(api.PortContext):
         self._bound_vif_type = vif_type
         self._bound_vif_details = vif_details
 
+    def allocate_dynamic_segment(self, segment):
+        pass
+
+    def release_dynamic_segment(self, segment_id):
+        pass
+
 
 class AgentMechanismBaseTestCase(base.BaseTestCase):
     # The following must be overridden for the specific mechanism
index b01aab0b97b58d3f4a79d9f0c9c24745342a4a0d..8b61ce87dc065374f16d17732407bc4004b54e03 100644 (file)
@@ -69,8 +69,12 @@ class Ml2PluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
                                      group='ml2')
         self.physnet = 'physnet1'
         self.vlan_range = '1:100'
+        self.vlan_range2 = '200:300'
+        self.physnet2 = 'physnet2'
         self.phys_vrange = ':'.join([self.physnet, self.vlan_range])
-        config.cfg.CONF.set_override('network_vlan_ranges', [self.phys_vrange],
+        self.phys2_vrange = ':'.join([self.physnet2, self.vlan_range2])
+        config.cfg.CONF.set_override('network_vlan_ranges',
+                                     [self.phys_vrange, self.phys2_vrange],
                                      group='ml2_type_vlan')
         super(Ml2PluginV2TestCase, self).setUp(PLUGIN_NAME,
                                                service_plugins=service_plugins)
@@ -368,6 +372,95 @@ class TestMultiSegmentNetworks(Ml2PluginV2TestCase):
     def setUp(self, plugin=None):
         super(TestMultiSegmentNetworks, self).setUp()
 
+    def test_allocate_dynamic_segment(self):
+        data = {'network': {'name': 'net1',
+                            'tenant_id': 'tenant_one'}}
+        network_req = self.new_create_request('networks', data)
+        network = self.deserialize(self.fmt,
+                                   network_req.get_response(self.api))
+        segment = {driver_api.NETWORK_TYPE: 'vlan',
+                   driver_api.PHYSICAL_NETWORK: 'physnet1'}
+        network_id = network['network']['id']
+        self.driver.type_manager.allocate_dynamic_segment(
+            self.context.session, network_id, segment)
+        dynamic_segment = ml2_db.get_dynamic_segment(self.context.session,
+                                                     network_id,
+                                                     'physnet1')
+        self.assertEqual('vlan', dynamic_segment[driver_api.NETWORK_TYPE])
+        self.assertEqual('physnet1',
+                         dynamic_segment[driver_api.PHYSICAL_NETWORK])
+        self.assertTrue(dynamic_segment[driver_api.SEGMENTATION_ID] > 0)
+        segment2 = {driver_api.NETWORK_TYPE: 'vlan',
+                    driver_api.SEGMENTATION_ID: 1234,
+                    driver_api.PHYSICAL_NETWORK: 'physnet3'}
+        self.driver.type_manager.allocate_dynamic_segment(
+            self.context.session, network_id, segment2)
+        dynamic_segment = ml2_db.get_dynamic_segment(self.context.session,
+                                                     network_id,
+                                                     segmentation_id='1234')
+        self.assertEqual('vlan', dynamic_segment[driver_api.NETWORK_TYPE])
+        self.assertEqual('physnet3',
+                         dynamic_segment[driver_api.PHYSICAL_NETWORK])
+        self.assertEqual(dynamic_segment[driver_api.SEGMENTATION_ID], 1234)
+
+    def test_allocate_dynamic_segment_multiple_physnets(self):
+        data = {'network': {'name': 'net1',
+                            'tenant_id': 'tenant_one'}}
+        network_req = self.new_create_request('networks', data)
+        network = self.deserialize(self.fmt,
+                                   network_req.get_response(self.api))
+        segment = {driver_api.NETWORK_TYPE: 'vlan',
+                   driver_api.PHYSICAL_NETWORK: 'physnet1'}
+        network_id = network['network']['id']
+        self.driver.type_manager.allocate_dynamic_segment(
+            self.context.session, network_id, segment)
+        dynamic_segment = ml2_db.get_dynamic_segment(self.context.session,
+                                                     network_id,
+                                                     'physnet1')
+        self.assertEqual('vlan', dynamic_segment[driver_api.NETWORK_TYPE])
+        self.assertEqual('physnet1',
+                         dynamic_segment[driver_api.PHYSICAL_NETWORK])
+        dynamic_segmentation_id = dynamic_segment[driver_api.SEGMENTATION_ID]
+        self.assertTrue(dynamic_segmentation_id > 0)
+        dynamic_segment1 = ml2_db.get_dynamic_segment(self.context.session,
+                                                      network_id,
+                                                      'physnet1')
+        dynamic_segment1_id = dynamic_segment1[driver_api.SEGMENTATION_ID]
+        self.assertEqual(dynamic_segmentation_id, dynamic_segment1_id)
+        segment2 = {driver_api.NETWORK_TYPE: 'vlan',
+                    driver_api.PHYSICAL_NETWORK: 'physnet2'}
+        self.driver.type_manager.allocate_dynamic_segment(
+            self.context.session, network_id, segment2)
+        dynamic_segment2 = ml2_db.get_dynamic_segment(self.context.session,
+                                                      network_id,
+                                                      'physnet2')
+        dynamic_segmentation2_id = dynamic_segment2[driver_api.SEGMENTATION_ID]
+        self.assertNotEqual(dynamic_segmentation_id, dynamic_segmentation2_id)
+
+    def test_allocate_release_dynamic_segment(self):
+        data = {'network': {'name': 'net1',
+                            'tenant_id': 'tenant_one'}}
+        network_req = self.new_create_request('networks', data)
+        network = self.deserialize(self.fmt,
+                                   network_req.get_response(self.api))
+        segment = {driver_api.NETWORK_TYPE: 'vlan',
+                   driver_api.PHYSICAL_NETWORK: 'physnet1'}
+        network_id = network['network']['id']
+        self.driver.type_manager.allocate_dynamic_segment(
+            self.context.session, network_id, segment)
+        dynamic_segment = ml2_db.get_dynamic_segment(self.context.session,
+                                                     network_id,
+                                                     'physnet1')
+        self.assertEqual('vlan', dynamic_segment[driver_api.NETWORK_TYPE])
+        self.assertEqual('physnet1',
+                         dynamic_segment[driver_api.PHYSICAL_NETWORK])
+        dynamic_segmentation_id = dynamic_segment[driver_api.SEGMENTATION_ID]
+        self.assertTrue(dynamic_segmentation_id > 0)
+        self.driver.type_manager.release_dynamic_segment(
+            self.context.session, dynamic_segment[driver_api.ID])
+        self.assertIsNone(ml2_db.get_dynamic_segment(
+            self.context.session, network_id, 'physnet1'))
+
     def test_create_network_provider(self):
         data = {'network': {'name': 'net1',
                             pnet.NETWORK_TYPE: 'vlan',
@@ -473,16 +566,62 @@ class TestMultiSegmentNetworks(Ml2PluginV2TestCase):
         res = network_req.get_response(self.api)
         self.assertEqual(201, res.status_int)
 
+    def test_release_network_segments(self):
+        data = {'network': {'name': 'net1',
+                            'admin_state_up': True,
+                            'shared': False,
+                            pnet.NETWORK_TYPE: 'vlan',
+                            pnet.PHYSICAL_NETWORK: 'physnet1',
+                            pnet.SEGMENTATION_ID: 1,
+                            'tenant_id': 'tenant_one'}}
+        network_req = self.new_create_request('networks', data)
+        res = network_req.get_response(self.api)
+        network = self.deserialize(self.fmt, res)
+        network_id = network['network']['id']
+        segment = {driver_api.NETWORK_TYPE: 'vlan',
+                   driver_api.PHYSICAL_NETWORK: 'physnet2'}
+        self.driver.type_manager.allocate_dynamic_segment(
+            self.context.session, network_id, segment)
+        dynamic_segment = ml2_db.get_dynamic_segment(self.context.session,
+                                                     network_id,
+                                                     'physnet2')
+        self.assertEqual('vlan', dynamic_segment[driver_api.NETWORK_TYPE])
+        self.assertEqual('physnet2',
+                         dynamic_segment[driver_api.PHYSICAL_NETWORK])
+        self.assertTrue(dynamic_segment[driver_api.SEGMENTATION_ID] > 0)
+
+        req = self.new_delete_request('networks', network_id)
+        res = req.get_response(self.api)
+        self.assertEqual(ml2_db.get_network_segments(
+            self.context.session, network_id), [])
+        self.assertIsNone(ml2_db.get_dynamic_segment(
+            self.context.session, network_id, 'physnet2'))
+
     def test_release_segment_no_type_driver(self):
+        data = {'network': {'name': 'net1',
+                            'admin_state_up': True,
+                            'shared': False,
+                            pnet.NETWORK_TYPE: 'vlan',
+                            pnet.PHYSICAL_NETWORK: 'physnet1',
+                            pnet.SEGMENTATION_ID: 1,
+                            'tenant_id': 'tenant_one'}}
+        network_req = self.new_create_request('networks', data)
+        res = network_req.get_response(self.api)
+        network = self.deserialize(self.fmt, res)
+        network_id = network['network']['id']
+
         segment = {driver_api.NETWORK_TYPE: 'faketype',
                    driver_api.PHYSICAL_NETWORK: 'physnet1',
                    driver_api.ID: 1}
         with mock.patch('neutron.plugins.ml2.managers.LOG') as log:
-            self.driver.type_manager.release_segment(session=None,
-                                                     segment=segment)
-        log.error.assert_called_once_with(
-            "Failed to release segment '%s' because "
-            "network type is not supported.", segment)
+            with mock.patch('neutron.plugins.ml2.managers.db') as db:
+                db.get_network_segments.return_value = (segment,)
+                self.driver.type_manager.release_network_segments(
+                    self.context.session, network_id)
+
+                log.error.assert_called_once_with(
+                    "Failed to release segment '%s' because "
+                    "network type is not supported.", segment)
 
     def test_create_provider_fail(self):
         segment = {pnet.NETWORK_TYPE: None,