Fixed bug that old VLAN ID is used in multi-POD environment

When a network spreads multiple PODs and port creation is required
just after port deletion for a same POD, allocation of VLAN ID for
a network segment (POD) can be racy. As a result, a corresponding
port creation on NWA fails because VLAN segment is released during
the operation.

To fix the bug, this commit makes the following changes:
* NWA plugin now deletes VLAN ID information from neutron
  DB before calling delete request to NWA.
* When the delete request completes and if VLAN ID of the requested
  segment is zero (which means another allocation request is ongogin),
  VLAN ID information is not touched.

Note that this bug does not occurs in a single POD environemnt.

Change-Id: I2fe86c54063310497ca0091200433e54bb16b198
Closes-Bug: #1569323
This commit is contained in:
Shinji YANAGIDA 2016-04-12 21:42:57 +09:00 committed by Akihiro Motoki
parent 1e8bf7394e
commit 9cbfd415f0
6 changed files with 118 additions and 3 deletions

View File

@ -433,6 +433,7 @@ class AgentProxyL2(object):
if 1 < gd_count:
nwa_data = self._delete_general_dev_data(
nwa_data=nwa_data, **kwargs)
self._delete_general_dev_segment(context, nwa_data, nwa_info)
raise nwa_exc.AgentProxyException(value=nwa_data)
# delete general dev
@ -514,6 +515,15 @@ class AgentProxyL2(object):
return nwa_data
def _delete_general_dev_segment(self, context, nwa_data, nwa_info):
network_id = nwa_info['network']['id']
physical_network = nwa_info['physical_network']
resource_group_name = nwa_info['resource_group_name']
if not check_segment_gd(network_id, resource_group_name, nwa_data):
self.nwa_l2_rpc.release_dynamic_segment_from_agent(
context, physical_network, network_id
)
@utils.log_method_return_value
@helpers.log_method_call
def _delete_general_dev(self, context, **kwargs):
@ -540,6 +550,7 @@ class AgentProxyL2(object):
if body['status'] == 'SUCCEED':
LOG.debug("DeleteGeneralDev SUCCEED")
nwa_data = self._delete_general_dev_data(**kwargs)
self._delete_general_dev_segment(context, nwa_data, nwa_info)
else:
LOG.debug("DeleteGeneralDev %s", body['status'])
raise nwa_exc.AgentProxyException(value=nwa_data)

View File

@ -181,6 +181,7 @@ class NECNWAMechanismDriver(ovs.OpenvswitchMechanismDriver):
try:
kwargs = self._make_l2api_kwargs(
context, use_original_port=use_original_port)
self._l2_delete_segment(context, kwargs['nwa_info'])
proxy = self._get_l2api_proxy(context, kwargs['tenant_id'])
kwargs['nwa_info'] = self._revert_dhcp_agent_device_id(
context, kwargs['nwa_info'])
@ -210,6 +211,16 @@ class NECNWAMechanismDriver(ovs.OpenvswitchMechanismDriver):
)
return nwa_info
def _l2_delete_segment(self, context, nwa_info):
session = context.network._plugin_context.session
del_segment = db.get_dynamic_segment(
session,
context.network.current['id'],
physical_network=nwa_info['physical_network'])
if del_segment:
LOG.debug('delete_network_segment %s', del_segment)
db.delete_network_segment(session, del_segment['id'])
def _l3_create_tenant_fw(self, context):
device_owner = context._port['device_owner']
grplst = [res['device_owner'] for res in self.resource_groups]

View File

@ -133,6 +133,6 @@ class NwaL2ServerRpcCallback(object):
session, network_id, physical_network=physical_network,
)
if del_segment:
LOG.debug("release_dynamic_segment segment_id=%s",
del_segment['id'])
db_ml2.delete_network_segment(session, del_segment['id'])
LOG.debug("release_dynamic_segment segment=%s", del_segment)
if del_segment['segmentation_id'] != 0:
db_ml2.delete_network_segment(session, del_segment['id'])

View File

@ -497,3 +497,15 @@ class TestNECNWAMechanismDriver(TestMechNwa):
self.context._port['device_owner'] = constants.DEVICE_OWNER_ROUTER_INTF
gntb.side_effect = Exception
self.driver._bind_port_nwa(self.context)
@patch('neutron.plugins.ml2.db.get_dynamic_segment')
@patch('neutron.plugins.ml2.db.delete_network_segment')
def test__l2_delete_segment(self, dns, gds):
gds.return_value = None
self.driver._l2_delete_segment(self.context, MagicMock())
self.assertEqual(0, dns.call_count)
dns.mock_reset()
gds.return_value = {'id': 'ID-100'}
self.driver._l2_delete_segment(self.context, MagicMock())
self.assertEqual(1, dns.call_count)

View File

@ -0,0 +1,34 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# 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.
import mock
from neutron.tests import base
from networking_nec.nwa.l2.rpc import nwa_l2_server_api
class TestNwaL2ServerApi(base.BaseTestCase):
@mock.patch('neutron.common.rpc.get_client')
def setUp(self, f1):
super(TestNwaL2ServerApi, self).setUp()
self.proxy = nwa_l2_server_api.NwaL2ServerRpcApi("dummy-topic")
self.context = mock.MagicMock()
def test_release_dynamic_segment_from_agent(self):
cctxt = mock.MagicMock()
self.proxy.client.prepare.return_value = cctxt
self.proxy.release_dynamic_segment_from_agent(
self.context, 'physical_network', 'network_id')
self.assertEqual(1, cctxt.call.call_count)

View File

@ -0,0 +1,47 @@
# Copyright 2016 NEC Corporation. All rights reserved.
#
# 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.
import mock
from neutron.tests import base
from networking_nec.nwa.l2.rpc import nwa_l2_server_callback
class TestNwaL2ServerRpcCallback(base.BaseTestCase):
def setUp(self):
super(TestNwaL2ServerRpcCallback, self).setUp()
self.callback = nwa_l2_server_callback.NwaL2ServerRpcCallback()
self.context = mock.MagicMock()
@mock.patch('neutron.db.api.get_session')
@mock.patch('neutron.plugins.ml2.db.get_dynamic_segment')
@mock.patch('neutron.plugins.ml2.db.delete_network_segment')
def test_release_dynamic_segment_from_agent(self, dns, gds, gs):
del_segment = {'segmentation_id': 0, 'id': 'ID-0'}
gds.return_value = del_segment
self.callback.release_dynamic_segment_from_agent(
self.context,
network_id='network-id',
physical_network='physical-network')
self.assertEqual(0, dns.call_count)
dns.reset_mock()
del_segment = {'segmentation_id': 4000, 'id': 'ID-4000'}
gds.return_value = del_segment
self.callback.release_dynamic_segment_from_agent(
self.context,
network_id='network-id',
physical_network='physical-network')
self.assertEqual(1, dns.call_count)