[L2] no provisioning block for internal service port

Provisioning Blocks [1] was introduced to manage composite
object status. Port object is the one Neutron sets provisioning
block during the port processing life cycle. Here is the compute
port (VM's NIC port) processing procedure:
1. nova creates port
2. the 'openvswitch' mechinism driver inserts provisioning block
   for this port
3. nova calls related interface to plug the device
4. L2-agent sets the flows (or rules/devices) for the port and
   call update_device_list to neutron-server
5. neutron-server try to set port status to ACTIVE
6. neutron-server notify nova that "vif-plugged" success

This works fine for VM with its ports. But for neutron service port,
like router_gateway, router_interface and dhcp, it is unnecessary.
Because there is no dependency among neutron resources. Neutron
just knows that the ports had been set properly. And another thing
is, for most of these internal service port, there is no need of
DHCP, security group or port security.

So for neutron internal service ports, the procesure can be:
1. neutron L3/DHCP/X related service plugin creates port
2. no provisioning_block
3. L3/DHCP/X related agent plug the port
4. L2-agent sets the flows (or rules/devices) for the port and
   call update_device_list to neutron-server
5. neutron-server sets port status to ACTIVE directly, then done!

This patch will set neutron *AgentMechanismDrver (including built-in
drivers: linuxbridge, macvtap, sriov, openvswitch) to skip inserting
the provisioning_block for Neutron internal service ports.

[1] https://docs.openstack.org/neutron/latest/contributor/internals/provisioning_blocks.html

Closes-Bug: #1930432
Change-Id: Iaf7788bf0cba19a693cbf456f98e50d7b5de9e41
This commit is contained in:
LIU Yulong 2021-06-02 13:23:44 +08:00
parent 3127bd1d57
commit e0ea4a51ba
4 changed files with 71 additions and 3 deletions

View File

@ -0,0 +1,30 @@
#
# 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 neutron_lib import constants
DEFAULT_DEVICE_OWNER = ''
# TODO(liuyulong): move to neutron-lib or common constants
NO_PBLOCKS_TYPES = [
DEFAULT_DEVICE_OWNER,
constants.DEVICE_OWNER_DVR_INTERFACE,
constants.DEVICE_OWNER_HA_REPLICATED_INT,
constants.DEVICE_OWNER_ROUTER_INTF,
constants.DEVICE_OWNER_ROUTER_GW,
constants.DEVICE_OWNER_ROUTER_SNAT,
constants.DEVICE_OWNER_DHCP,
constants.DEVICE_OWNER_AGENT_GW,
constants.DEVICE_OWNER_ROUTER_HA_INTF,
constants.DEVICE_OWNER_FLOATINGIP,
]

View File

@ -25,6 +25,7 @@ from oslo_log import log
from neutron._i18n import _
from neutron.db import provisioning_blocks
from neutron.plugins.ml2.common import constants as ml2_consts
LOG = log.getLogger(__name__)
@ -71,6 +72,11 @@ class AgentMechanismDriverBase(api.MechanismDriver, metaclass=abc.ABCMeta):
if not context.host or port['status'] == const.PORT_STATUS_ACTIVE:
# no point in putting in a block if the status is already ACTIVE
return
if port['device_owner'] in ml2_consts.NO_PBLOCKS_TYPES:
# do not set provisioning_block if it is neutron service port
return
vnic_type = context.current.get(portbindings.VNIC_TYPE,
portbindings.VNIC_NORMAL)
if vnic_type not in self.supported_vnic_types:

View File

@ -125,6 +125,7 @@ from neutron.extensions import vlantransparent
from neutron.ipam import exceptions as ipam_exc
from neutron.objects import base as base_obj
from neutron.objects import ports as ports_obj
from neutron.plugins.ml2.common import constants as ml2_consts
from neutron.plugins.ml2.common import exceptions as ml2_exc
from neutron.plugins.ml2 import db
from neutron.plugins.ml2 import driver_context
@ -1405,6 +1406,11 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
def _setup_dhcp_agent_provisioning_component(self, context, port):
if not cfg.CONF.enable_traditional_dhcp:
return
if port['device_owner'] in ml2_consts.NO_PBLOCKS_TYPES:
# do not set provisioning_block if it is neutron service port
return
subnet_ids = [f['subnet_id'] for f in port['fixed_ips']]
if (db.is_dhcp_active_on_any_subnet(context, subnet_ids) and
len(self.get_dhcp_agents_hosting_networks(context,

View File

@ -55,6 +55,7 @@ from neutron.db import segments_db
from neutron.objects import base as base_obj
from neutron.objects import ports as port_obj
from neutron.objects import router as l3_obj
from neutron.plugins.ml2.common import constants as ml2_consts
from neutron.plugins.ml2.common import exceptions as ml2_exc
from neutron.plugins.ml2 import db as ml2_db
from neutron.plugins.ml2 import driver_context
@ -1204,7 +1205,7 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase):
self._add_fake_dhcp_agent()
with mock.patch.object(provisioning_blocks,
'add_provisioning_component') as ap:
with self.port():
with self.port(device_owner="fake:test"):
self.assertTrue(ap.called)
def test_dhcp_provisioning_blocks_skipped_on_create_with_no_dhcp(self):
@ -1220,7 +1221,7 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase):
ctx = context.get_admin_context()
plugin = directory.get_plugin()
self._add_fake_dhcp_agent()
with self.port() as port:
with self.port(device_owner="fake:test") as port:
with mock.patch.object(provisioning_blocks,
'add_provisioning_component') as ap:
port['port'].update(update_dict)
@ -1239,9 +1240,34 @@ class TestMl2PortsV2(test_plugin.TestPortsV2, Ml2PluginV2TestCase):
def test_dhcp_provisioning_blocks_removed_without_dhcp_agents(self):
with mock.patch.object(provisioning_blocks,
'remove_provisioning_component') as cp:
with self.port():
with self.port(device_owner="fake:test"):
self.assertTrue(cp.called)
def _test_no_dhcp_provisioning_blocks_removed_empty_device_owner(
self, device_owner):
with mock.patch.object(provisioning_blocks,
'remove_provisioning_component') as cp:
with self.port(device_owner=device_owner):
self.assertFalse(cp.called)
def _test_no_dhcp_provisioning_blocks_added_empty_device_owner(
self, device_owner):
with mock.patch.object(provisioning_blocks,
'add_provisioning_component') as cp:
with self.port(device_owner=device_owner):
self.assertFalse(cp.called)
def test_no_dhcp_provisioning_blocks_removed_for_empty_or_service_port(
self):
for device_owner in ml2_consts.NO_PBLOCKS_TYPES:
self._test_no_dhcp_provisioning_blocks_removed_empty_device_owner(
device_owner)
def test_no_dhcp_provisioning_blocks_added_for_empty_or_service_port(self):
for device_owner in ml2_consts.NO_PBLOCKS_TYPES:
self._test_no_dhcp_provisioning_blocks_added_empty_device_owner(
device_owner)
def test_create_update_get_port_same_fixed_ips_order(self):
ctx = context.get_admin_context()
plugin = directory.get_plugin()