--- /dev/null
+# 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')
-58fe87a01143
+236b90af57ab
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,
'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):
"""
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):
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):
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):
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):
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.
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
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)
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',
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,