Set bridge_name in OVS trunk port's vif_details

This patch follows up on the Nova work that allows Neutron to pass an
OVS 'bridge_name' in a port's vif_details [1] and the os-vif work that
ensures the OVS bridge passed in as 'bridge_name' (or the config default
of 'br-int') is created [2].

If the port is a trunk's parent port, then bridge_name will be set to a
generated trunk bridge name for vlan-aware VMs. Otherwise, the
bridge_name field will remain empty.

Note: until Nova integrates os-vif [3] and uses a release that contains
the change in [2], the trunk bridge will not actually be created by
Nova/os-vif. It is expected to be integrated for the Newton release.
Until then, this Nova-equivalent patch can be used for testing
purposes [4].

[1] https://review.openstack.org/#/c/260700/
[2] https://review.openstack.org/#/c/330818/
[3] https://review.openstack.org/#/c/269672/
[4] https://review.openstack.org/#/c/332474/

Partially-implements: blueprint vlan-aware-vms
Change-Id: Iad72d163bc406df65866f6d962abcb78596828e7
This commit is contained in:
Rawlin Peters
2016-07-28 12:23:50 -06:00
parent 46b6e129ff
commit f697cddda6
10 changed files with 164 additions and 2 deletions

View File

@@ -50,6 +50,7 @@ VIF_DETAILS_VLAN = 'vlan'
VIF_DETAILS_MACVTAP_SOURCE = 'macvtap_source'
VIF_DETAILS_MACVTAP_MODE = 'macvtap_mode'
VIF_DETAILS_PHYSICAL_INTERFACE = 'physical_interface'
VIF_DETAILS_BRIDGE_NAME = 'bridge_name'
# The keys below are used in the VIF_DETAILS attribute to convey
# information related to the configuration of the vhost-user VIF driver.

View File

@@ -142,3 +142,6 @@ OPENFLOW14 = "OpenFlow14"
# A placeholder for dead vlans.
DEAD_VLAN_TAG = p_const.MAX_VLAN_TAG + 1
# callback resource for setting 'bridge_name' in the 'binding:vif_details'
OVS_BRIDGE_NAME = 'ovs_bridge_name'

View File

@@ -19,6 +19,8 @@ from neutron_lib import constants
from oslo_config import cfg
from neutron.agent import securitygroups_rpc
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.extensions import portbindings
from neutron.plugins.common import constants as p_constants
from neutron.plugins.ml2 import driver_api as api
@@ -86,6 +88,24 @@ class OpenvswitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
return self.vif_type
def get_vif_details(self, agent, context):
vif_details = self._pre_get_vif_details(agent, context)
self._set_bridge_name(context.current, vif_details)
return vif_details
@staticmethod
def _set_bridge_name(port, vif_details):
# REVISIT(rawlin): add BridgeName as a nullable column to the Port
# model and simply check here if it's set and insert it into the
# vif_details.
def set_bridge_name_inner(bridge_name):
vif_details[portbindings.VIF_DETAILS_BRIDGE_NAME] = bridge_name
registry.notify(
a_const.OVS_BRIDGE_NAME, events.BEFORE_READ,
set_bridge_name_inner, port=port)
def _pre_get_vif_details(self, agent, context):
a_config = agent['configurations']
if a_config.get('datapath_type') != a_const.OVS_DATAPATH_NETDEV:
details = dict(self.vif_details)

View File

@@ -0,0 +1,15 @@
# (c) Copyright 2016 Hewlett Packard Enterprise Development LP
#
# 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.
TRUNK_BR_PREFIX = 'tbr-'

View File

@@ -15,9 +15,14 @@ from neutron_lib import constants
from oslo_config import cfg
from oslo_log import log as logging
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.extensions import portbindings
from neutron.plugins.ml2.drivers.openvswitch.agent.common import (
constants as agent_consts)
from neutron.services.trunk import constants as trunk_consts
from neutron.services.trunk.drivers import base
from neutron.services.trunk import utils
LOG = logging.getLogger(__name__)
@@ -53,4 +58,15 @@ def register():
"""Register the driver."""
global DRIVER
DRIVER = OVSDriver.create()
# To set the bridge_name in a parent port's vif_details.
registry.subscribe(vif_details_bridge_name_handler,
agent_consts.OVS_BRIDGE_NAME,
events.BEFORE_READ)
LOG.debug('Open vSwitch trunk driver registered')
def vif_details_bridge_name_handler(resource, event, set_br_name, **kwargs):
"""If port is a trunk port, generate a bridge_name for its vif_details."""
port = kwargs['port']
if 'trunk_details' in port:
set_br_name(utils.gen_trunk_br_name(port['trunk_details']['trunk_id']))

View File

@@ -0,0 +1,22 @@
# (c) Copyright 2016 Hewlett Packard Enterprise Development LP
#
# 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
from neutron.services.trunk.drivers.openvswitch import constants as ovs_const
def gen_trunk_br_name(trunk_id):
return ((ovs_const.TRUNK_BR_PREFIX + trunk_id)
[:constants.DEVICE_NAME_MAX_LEN - 1])

View File

@@ -0,0 +1,51 @@
# (c) Copyright 2016 Hewlett Packard Enterprise Development LP
#
# 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.extensions import portbindings as pb
from neutron.services.trunk import plugin as trunk_plugin
from neutron.services.trunk import utils as trunk_utils
from neutron.tests.common import helpers
from neutron.tests.unit.plugins.ml2 import base as ml2_test_base
class TestTrunkServicePlugin(ml2_test_base.ML2TestFramework):
def setUp(self):
super(TestTrunkServicePlugin, self).setUp()
self.trunk_plugin = trunk_plugin.TrunkPlugin()
def test_ovs_bridge_name_set_when_trunk_bound(self):
helpers.register_ovs_agent(host=helpers.HOST)
with self.port() as port:
trunk_port_id = port['port']['id']
trunk_req = {'port_id': trunk_port_id,
'tenant_id': 'test_tenant',
'sub_ports': []}
trunk_res = self.trunk_plugin.create_trunk(self.context,
{'trunk': trunk_req})
port['port'][pb.HOST_ID] = helpers.HOST
bound_port = self.core_plugin.update_port(self.context,
trunk_port_id, port)
self.assertEqual(
trunk_utils.gen_trunk_br_name(trunk_res['id']),
bound_port[pb.VIF_DETAILS][pb.VIF_DETAILS_BRIDGE_NAME])
def test_ovs_bridge_name_not_set_when_not_trunk(self):
helpers.register_ovs_agent(host=helpers.HOST)
with self.port() as port:
port['port'][pb.HOST_ID] = helpers.HOST
bound_port = self.core_plugin.update_port(self.context,
port['port']['id'], port)
self.assertIsNone(
bound_port[pb.VIF_DETAILS].get(pb.VIF_DETAILS_BRIDGE_NAME))

View File

@@ -16,10 +16,14 @@
from neutron_lib import constants
from oslo_config import cfg
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.extensions import portbindings
from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2.drivers.openvswitch.mech_driver \
import mech_openvswitch
from neutron.plugins.ml2.drivers.openvswitch.agent.common import (
constants as a_const)
from neutron.plugins.ml2.drivers.openvswitch.mech_driver import (
mech_openvswitch)
from neutron.tests.unit.plugins.ml2 import _test_mech_agent as base
@@ -59,6 +63,19 @@ class OpenvswitchMechanismBaseTestCase(base.AgentMechanismBaseTestCase):
self.driver = mech_openvswitch.OpenvswitchMechanismDriver()
self.driver.initialize()
def test__set_bridge_name_notify(self):
def fake_callback(resource, event, trigger, **kwargs):
trigger('fake-br-name')
registry.subscribe(fake_callback, a_const.OVS_BRIDGE_NAME,
events.BEFORE_READ)
fake_vif_details = {}
self.driver._set_bridge_name('foo', fake_vif_details)
self.assertEqual(
'fake-br-name',
fake_vif_details.get(portbindings.VIF_DETAILS_BRIDGE_NAME, ''))
class OpenvswitchMechanismSGDisabledBaseTestCase(
OpenvswitchMechanismBaseTestCase):

View File

@@ -11,9 +11,15 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
from neutron_lib import constants
from oslo_config import cfg
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.plugins.ml2.drivers.openvswitch.agent.common import (
constants as agent_consts)
from neutron.services.trunk.drivers.openvswitch import driver
from neutron.tests import base
@@ -35,3 +41,14 @@ class OVSDriverTestCase(base.BaseTestCase):
'openvswitch', group='ml2')
ovs_driver = driver.OVSDriver.create()
self.assertTrue(ovs_driver.is_loaded)
@mock.patch('neutron.services.trunk.utils.gen_trunk_br_name')
def test_vif_details_bridge_name_handler_registration(self,
mock_gen_br_name):
driver.register()
mock_gen_br_name.return_value = 'fake-trunk-br-name'
test_trigger = mock.Mock()
registry.notify(agent_consts.OVS_BRIDGE_NAME, events.BEFORE_READ,
test_trigger, **{'port': {'trunk_details':
{'trunk_id': 'foo'}}})
test_trigger.assert_called_once_with('fake-trunk-br-name')