Merge "Adds trunked VLANs support"

This commit is contained in:
Jenkins 2017-01-27 10:55:52 +00:00 committed by Gerrit Code Review
commit 83f8a20c82
9 changed files with 335 additions and 25 deletions

View File

@ -250,12 +250,7 @@ networking-plugin-hyperv_agent.html
self._utils.connect_vnic_to_vswitch(map['vswitch_name'], port_id)
if network_type == constants.TYPE_VLAN:
LOG.info(_LI('Binding VLAN ID %(segmentation_id)s '
'to switch port %(port_id)s'),
dict(segmentation_id=segmentation_id, port_id=port_id))
self._utils.set_vswitch_port_vlan_id(
segmentation_id,
port_id)
self._vlan_driver.bind_vlan_port(port_id, segmentation_id)
elif network_type == constants.TYPE_NVGRE and self._nvgre_enabled:
self._nvgre_ops.bind_nvgre_port(
segmentation_id, map['vswitch_name'], port_id)

View File

@ -34,6 +34,7 @@ from oslo_service import loopingcall
from hyperv.common.i18n import _, _LE, _LI # noqa
from hyperv.neutron import constants as h_const
from hyperv.neutron import hyperv_neutron_agent
from hyperv.neutron import trunk_driver
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@ -151,6 +152,8 @@ class HyperVNeutronAgent(hyperv_neutron_agent.HyperVNeutronAgentMixin):
self._report_state)
heartbeat.start(interval=report_interval)
self._vlan_driver = trunk_driver.HyperVTrunkDriver(self.context)
def main():
config.register_agent_state_opts_helper(cfg.CONF)

View File

@ -0,0 +1,147 @@
# Copyright 2017 Cloudbase Solutions Srl
# 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.
from neutron.api.rpc.callbacks import events
from neutron.api.rpc.handlers import resources_rpc
from neutron.services.trunk import constants as t_const
from neutron.services.trunk.rpc import agent as trunk_rpc
from os_win import constants as os_win_const
from os_win import utilsfactory
from oslo_log import log as logging
import oslo_messaging
from hyperv.common.i18n import _LI, _LE # noqa
LOG = logging.getLogger(__name__)
class HyperVTrunkDriver(trunk_rpc.TrunkSkeleton):
"""Driver responsible for handling trunk/subport/port events.
Receives data model events from the neutron server and uses them to setup
VLAN trunks for Hyper-V vSwitch ports.
"""
def __init__(self, context):
super(HyperVTrunkDriver, self).__init__()
self._context = context
self._utils = utilsfactory.get_networkutils()
self._trunk_rpc = trunk_rpc.TrunkStub()
# Map between trunk.id and trunk.
self._trunks = {}
def handle_trunks(self, trunks, event_type):
"""Trunk data model change from the server."""
LOG.debug("Trunks event received: %(event_type)s. Trunks: %(trunks)s",
{'event_type': event_type, 'trunks': trunks})
if event_type == events.DELETED:
# The port trunks have been deleted. Remove them from cache.
for trunk in trunks:
self._trunks.pop(trunk.id, None)
else:
for trunk in trunks:
self._trunks[trunk.id] = trunk
self._setup_trunk(trunk)
def handle_subports(self, subports, event_type):
"""Subport data model change from the server."""
LOG.debug("Subports event received: %(event_type)s. "
"Subports: %(subports)s",
{'event_type': event_type, 'subports': subports})
# update the cache.
if event_type == events.CREATED:
for subport in subports:
trunk = self._trunks.get(subport['trunk_id'])
if trunk:
trunk.sub_ports.append(subport)
elif event_type == events.DELETED:
for subport in subports:
trunk = self._trunks.get(subport['trunk_id'])
if trunk and subport in trunk.sub_ports:
trunk.sub_ports.remove(subport)
# update the bound trunks.
affected_trunk_ids = set([s['trunk_id'] for s in subports])
for trunk_id in affected_trunk_ids:
trunk = self._trunks.get(trunk_id)
if trunk:
self._setup_trunk(trunk)
def bind_vlan_port(self, port_id, segmentation_id):
trunk = self._fetch_trunk(port_id)
if not trunk:
# No trunk found. No VLAN IDs to set in trunk mode.
self._set_port_vlan(port_id, segmentation_id)
return
self._setup_trunk(trunk, segmentation_id)
def _fetch_trunk(self, port_id, context=None):
context = context or self._context
try:
trunk = self._trunk_rpc.get_trunk_details(context, port_id)
LOG.debug("Found trunk for port_id %(port_id)s: %(trunk)s",
{'port_id': port_id, 'trunk': trunk})
# cache it.
self._trunks[trunk.id] = trunk
return trunk
except resources_rpc.ResourceNotFound:
return None
except oslo_messaging.RemoteError as ex:
if 'CallbackNotFound' not in str(ex):
raise
LOG.debug("Trunk plugin disabled on server. Assuming port %s is "
"not a trunk.", port_id)
return None
def _setup_trunk(self, trunk, vlan_id=None):
"""Sets up VLAN trunk and updates the trunk status."""
LOG.info(_LI('Binding trunk port: %s.'), trunk)
try:
# bind sub_ports to host.
self._trunk_rpc.update_subport_bindings(self._context,
trunk.sub_ports)
vlan_trunk = [s.segmentation_id for s in trunk.sub_ports]
self._set_port_vlan(trunk.port_id, vlan_id, vlan_trunk)
self._trunk_rpc.update_trunk_status(self._context, trunk.id,
t_const.ACTIVE_STATUS)
except Exception:
# something broke
LOG.exception(_LE("Failure setting up subports for %s"),
trunk.port_id)
self._trunk_rpc.update_trunk_status(self._context, trunk.id,
t_const.DEGRADED_STATUS)
def _set_port_vlan(self, port_id, vlan_id, vlan_trunk=None):
LOG.info(_LI('Binding VLAN ID: %(vlan_id)s, VLAN trunk: '
'%(vlan_trunk)s to switch port %(port_id)s'),
dict(vlan_id=vlan_id, vlan_trunk=vlan_trunk, port_id=port_id))
op_mode = (os_win_const.VLAN_MODE_TRUNK if vlan_trunk else
os_win_const.VLAN_MODE_ACCESS)
self._utils.set_vswitch_port_vlan_id(
vlan_id,
port_id,
operation_mode=op_mode,
vlan_trunk=vlan_trunk)

View File

@ -25,6 +25,7 @@ import traceback
import eventlet.timeout
import fixtures
import mock
from os_win import utilsfactory
from oslo_config import cfg
from oslo_utils import strutils
import testtools
@ -136,3 +137,12 @@ class BaseTestCase(testtools.TestCase):
group = kw.pop('group', None)
for k, v in kw.items():
CONF.set_override(k, v, group)
class HyperVBaseTestCase(BaseTestCase):
def setUp(self):
super(HyperVBaseTestCase, self).setUp()
utilsfactory_patcher = mock.patch.object(utilsfactory, '_get_class')
utilsfactory_patcher.start()
self.addCleanup(utilsfactory_patcher.stop)

View File

@ -20,7 +20,6 @@ Unit tests for Windows Hyper-V virtual switch neutron driver
import mock
from os_win import exceptions
from os_win import utilsfactory
from oslo_config import cfg
from hyperv.neutron import constants
@ -31,15 +30,12 @@ from hyperv.tests import base
CONF = cfg.CONF
class TestHyperVNeutronAgent(base.BaseTestCase):
class TestHyperVNeutronAgent(base.HyperVBaseTestCase):
_FAKE_PORT_ID = 'fake_port_id'
def setUp(self):
super(TestHyperVNeutronAgent, self).setUp()
utilsfactory_patcher = mock.patch.object(utilsfactory, '_get_class')
utilsfactory_patcher.start()
self.addCleanup(utilsfactory_patcher.stop)
self.agent = hyperv_neutron_agent.HyperVNeutronAgentMixin()
self.agent._qos_ext = mock.MagicMock()
@ -54,6 +50,7 @@ class TestHyperVNeutronAgent(base.BaseTestCase):
self.agent.notifier = mock.Mock()
self.agent._utils = mock.MagicMock()
self.agent._nvgre_ops = mock.MagicMock()
self.agent._vlan_driver = mock.MagicMock()
def test_load_physical_network_mappings(self):
test_mappings = ['fakenetwork1:fake_vswitch',
@ -338,9 +335,7 @@ class TestHyperVNeutronAgent(base.BaseTestCase):
@mock.patch.object(hyperv_neutron_agent.HyperVNeutronAgentMixin,
'_provision_network')
def test_port_bound_nvgre(self, mock_provision_network):
self.agent._nvgre_enabled = True
network_type = constants.TYPE_NVGRE
def _check_port_bound_net_type(self, mock_provision_network, network_type):
net_uuid = 'my-net-uuid'
fake_map = {'vswitch_name': mock.sentinel.vswitch_name,
'ports': []}
@ -360,6 +355,17 @@ class TestHyperVNeutronAgent(base.BaseTestCase):
mock.sentinel.physical_network, mock.sentinel.segmentation_id)
self.agent._utils.connect_vnic_to_vswitch.assert_called_once_with(
mock.sentinel.vswitch_name, mock.sentinel.port_id)
def test_port_bound_vlan(self):
self._check_port_bound_net_type(network_type=constants.TYPE_VLAN)
self.agent._vlan_driver.bind_vlan_port.assert_called_once_with(
mock.sentinel.port_id, mock.sentinel.segmentation_id)
def test_port_bound_nvgre(self):
self.agent._nvgre_enabled = True
self._check_port_bound_net_type(network_type=constants.TYPE_NVGRE)
self.agent._nvgre_ops.bind_nvgre_port.assert_called_once_with(
mock.sentinel.segmentation_id, mock.sentinel.vswitch_name,
mock.sentinel.port_id)

View File

@ -119,6 +119,7 @@ class TestHyperVNeutronAgent(base.BaseTestCase):
self.agent.context, {'start_flag': True})
self.assertTrue(self.agent.agent_state['start_flag'])
@mock.patch.object(l2_agent.trunk_driver, 'HyperVTrunkDriver')
@mock.patch.object(l2_agent.qos_extension, 'QosAgentExtension')
@mock.patch.object(l2_agent.loopingcall, 'FixedIntervalLoopingCall')
@mock.patch.object(l2_agent.n_rpc, 'get_client')
@ -128,7 +129,7 @@ class TestHyperVNeutronAgent(base.BaseTestCase):
@mock.patch.object(l2_agent, 'CONF')
def test_setup_rpc(self, mock_CONF, mock_agent_rpc, mock_SGRpcApi,
mock_HyperVSecurityAgent, mock_get_client,
mock_LoopingCall, mock_qos_ext):
mock_LoopingCall, mock_qos_ext, mock_HyperVTrunkDriver):
mock_CONF.NVGRE.enable_support = True
mock_CONF.AGENT.report_interval = mock.sentinel.report_interval
mock_CONF.AGENT.enable_qos_extension = True
@ -144,6 +145,8 @@ class TestHyperVNeutronAgent(base.BaseTestCase):
self.assertEqual(mock_agent_rpc.create_consumers.return_value,
self.agent.connection)
self.assertEqual(mock_get_client.return_value, self.agent.client)
self.assertEqual(mock_HyperVTrunkDriver.return_value,
self.agent._vlan_driver)
mock_HyperVSecurityAgent.assert_called_once_with(
self.agent.context, self.agent.sg_plugin_rpc)
@ -163,6 +166,7 @@ class TestHyperVNeutronAgent(base.BaseTestCase):
self.agent.connection.consume_in_threads.assert_called_once_with()
mock_LoopingCall.return_value.start.assert_called_once_with(
interval=mock.sentinel.report_interval)
mock_HyperVTrunkDriver.assert_called_once_with(self.agent.context)
class TestMain(base.BaseTestCase):

View File

@ -18,7 +18,6 @@ Unit tests for Windows Hyper-V NVGRE driver.
"""
import mock
from os_win import utilsfactory
from oslo_config import cfg
from hyperv.neutron import constants
@ -28,7 +27,7 @@ from hyperv.tests import base
CONF = cfg.CONF
class TestHyperVNvgreOps(base.BaseTestCase):
class TestHyperVNvgreOps(base.HyperVBaseTestCase):
FAKE_MAC_ADDR = 'fa:ke:ma:ca:dd:re:ss'
FAKE_CIDR = '10.0.0.0/24'
@ -36,9 +35,6 @@ class TestHyperVNvgreOps(base.BaseTestCase):
def setUp(self):
super(TestHyperVNvgreOps, self).setUp()
utilsfactory_patcher = mock.patch.object(utilsfactory, '_get_class')
utilsfactory_patcher.start()
self.addCleanup(utilsfactory_patcher.stop)
self.context = 'context'
self.ops = nvgre_ops.HyperVNvgreOps([])

View File

@ -19,13 +19,12 @@ Unit tests for the Hyper-V Security Groups Driver.
import mock
from os_win import exceptions
from os_win import utilsfactory
from hyperv.neutron import security_groups_driver as sg_driver
from hyperv.tests import base
class SecurityGroupRuleTestHelper(base.BaseTestCase):
class SecurityGroupRuleTestHelper(base.HyperVBaseTestCase):
_FAKE_DIRECTION = 'egress'
_FAKE_ETHERTYPE = 'IPv4'
_FAKE_ETHERTYPE_IPV6 = 'IPv6'
@ -68,9 +67,6 @@ class TestHyperVSecurityGroupsDriver(SecurityGroupRuleTestHelper):
def setUp(self):
super(TestHyperVSecurityGroupsDriver, self).setUp()
utilsfactory_patcher = mock.patch.object(utilsfactory, '_get_class')
utilsfactory_patcher.start()
self.addCleanup(utilsfactory_patcher.stop)
self._driver = sg_driver.HyperVSecurityGroupsDriver()
self._driver._utils = mock.MagicMock()

View File

@ -0,0 +1,153 @@
# Copyright 2017 Cloudbase Solutions Srl
# 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.
"""
Unit tests for the Hyper-V Trunk Driver.
"""
import mock
from neutron.api.rpc.callbacks import events
from neutron.api.rpc.handlers import resources_rpc
from neutron.services.trunk import constants as t_const
from os_win import constants as os_win_const
import oslo_messaging
import testtools
from hyperv.neutron import trunk_driver
from hyperv.tests import base
class TestHyperVTrunkDriver(base.HyperVBaseTestCase):
@mock.patch.object(trunk_driver.trunk_rpc, 'TrunkStub',
lambda *args, **kwargs: None)
@mock.patch.object(trunk_driver.trunk_rpc.TrunkSkeleton, '__init__',
lambda *args, **kwargs: None)
def setUp(self):
super(TestHyperVTrunkDriver, self).setUp()
self.trunk_driver = trunk_driver.HyperVTrunkDriver(
mock.sentinel.context)
self.trunk_driver._utils = mock.MagicMock()
self.trunk_driver._trunk_rpc = mock.MagicMock()
def test_handle_trunks_deleted(self):
mock_trunk = mock.MagicMock()
self.trunk_driver._trunks[mock_trunk.id] = mock_trunk
self.trunk_driver.handle_trunks([mock_trunk], events.DELETED)
self.assertNotIn(mock_trunk.id, self.trunk_driver._trunks)
@mock.patch.object(trunk_driver.HyperVTrunkDriver, '_setup_trunk')
def test_handle_trunks_created(self, mock_setup_trunk):
sub_ports = []
mock_trunk = mock.MagicMock(sub_ports=sub_ports)
self.trunk_driver.handle_trunks([mock_trunk], events.CREATED)
self.assertEqual(mock_trunk, self.trunk_driver._trunks[mock_trunk.id])
mock_setup_trunk.assert_called_once_with(mock_trunk)
@mock.patch.object(trunk_driver.HyperVTrunkDriver, '_set_port_vlan')
@mock.patch.object(trunk_driver.HyperVTrunkDriver, '_fetch_trunk')
def test_bind_vlan_port_not_trunk(self, mock_fetch_trunk, mock_set_vlan):
mock_fetch_trunk.return_value = None
self.trunk_driver.bind_vlan_port(mock.sentinel.port_id,
mock.sentinel.segmentation_id)
mock_fetch_trunk.assert_called_once_with(mock.sentinel.port_id)
mock_set_vlan.assert_called_once_with(mock.sentinel.port_id,
mock.sentinel.segmentation_id)
@mock.patch.object(trunk_driver.HyperVTrunkDriver, '_setup_trunk')
@mock.patch.object(trunk_driver.HyperVTrunkDriver, '_fetch_trunk')
def test_bind_vlan_port(self, mock_fetch_trunk, mock_setup_trunk):
self.trunk_driver.bind_vlan_port(mock.sentinel.port_id,
mock.sentinel.segmentation_id)
mock_fetch_trunk.assert_called_once_with(mock.sentinel.port_id)
mock_setup_trunk.assert_called_once_with(mock_fetch_trunk.return_value,
mock.sentinel.segmentation_id)
def test_fetch_trunk(self):
mock_trunk = (
self.trunk_driver._trunk_rpc.get_trunk_details.return_value)
trunk = self.trunk_driver._fetch_trunk(mock.sentinel.port_id,
mock.sentinel.context)
self.assertEqual(mock_trunk, trunk)
self.assertEqual(mock_trunk, self.trunk_driver._trunks[mock_trunk.id])
self.trunk_driver._trunk_rpc.get_trunk_details.assert_called_once_with(
mock.sentinel.context, mock.sentinel.port_id)
def test_fetch_trunk_resource_not_found(self):
self.trunk_driver._trunk_rpc.get_trunk_details.side_effect = (
resources_rpc.ResourceNotFound)
trunk = self.trunk_driver._fetch_trunk(mock.sentinel.port_id)
self.assertIsNone(trunk)
def test_fetch_trunk_resource_remote_error(self):
self.trunk_driver._trunk_rpc.get_trunk_details.side_effect = (
oslo_messaging.RemoteError('expected CallbackNotFound'))
trunk = self.trunk_driver._fetch_trunk(mock.sentinel.port_id)
self.assertIsNone(trunk)
def test_fetch_trunk_resource_remote_error_reraised(self):
self.trunk_driver._trunk_rpc.get_trunk_details.side_effect = (
oslo_messaging.RemoteError)
self.assertRaises(oslo_messaging.RemoteError,
self.trunk_driver._fetch_trunk,
mock.sentinel.port_id)
@mock.patch.object(trunk_driver.HyperVTrunkDriver, '_set_port_vlan')
def test_setup_trunk(self, mock_set_vlan):
mock_subport = mock.MagicMock()
mock_trunk = mock.MagicMock(sub_ports=[mock_subport])
trunk_rpc = self.trunk_driver._trunk_rpc
trunk_rpc.update_trunk_status.side_effect = [
testtools.ExpectedException, None]
self.trunk_driver._setup_trunk(mock_trunk, mock.sentinel.vlan_id)
trunk_rpc.update_subport_bindings.assert_called_once_with(
self.trunk_driver._context, [mock_subport])
mock_set_vlan.assert_called_once_with(
mock_trunk.port_id, mock.sentinel.vlan_id,
[mock_subport.segmentation_id])
mock_set_vlan.has_calls([
mock.call(self.trunk_driver._context, mock_trunk.id, status)
for status in [t_const.ACTIVE_STATUS, t_const.DEGRADED_STATUS]])
def _check_set_port_vlan(self, vlan_trunk, operation_mode):
self.trunk_driver._set_port_vlan(mock.sentinel.port_id,
mock.sentinel.vlan_id,
vlan_trunk)
self.trunk_driver._utils.set_vswitch_port_vlan_id(
mock.sentinel.vlan_id, mock.sentinel.port_id,
operation_mode=operation_mode,
vlan_trunk=vlan_trunk)
def test_set_port_vlan_trunk_mode(self):
self._check_set_port_vlan(mock.sentinel.vlan_trunk,
os_win_const.VLAN_MODE_TRUNK)
def test_set_port_vlan_access_mode(self):
self._check_set_port_vlan(None, os_win_const.VLAN_MODE_ACCESS)