Add layer 2 agent for Hyper-V Network Virtualization v2
The new layer 2 agent is required in order to interact with the Microsoft Network Controller. Co-Authored-By: Claudiu Belu <cbelu@cloudbasesolutions.com> Partially-Implements: blueprint hyperv-network-virtualization-support Change-Id: I019a4dca52881ab7eba895b85c039c8405da0628
This commit is contained in:
parent
9004336aa9
commit
86fa652c0e
|
@ -0,0 +1,116 @@
|
|||
# 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.
|
||||
|
||||
"""This module contains the L2 Agent needed for HNV."""
|
||||
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from neutron.common import config as common_config
|
||||
from neutron.conf.agent import common as neutron_config
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from hyperv.common.i18n import _LI # noqa
|
||||
from hyperv.neutron import _common_utils as c_util
|
||||
from hyperv.neutron.agent import layer2 as hyperv_base
|
||||
from hyperv.neutron import constants as h_const
|
||||
from hyperv.neutron import neutron_client
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
CONF.import_group('AGENT', 'hyperv.neutron.config')
|
||||
|
||||
_port_synchronized = c_util.get_port_synchronized_decorator('n-hv-agent-')
|
||||
|
||||
|
||||
class HNVAgent(hyperv_base.Layer2Agent):
|
||||
|
||||
_AGENT_BINARY = "neutron-hnv-agent"
|
||||
_AGENT_TYPE = h_const.AGENT_TYPE_HNV
|
||||
|
||||
def __init__(self):
|
||||
super(HNVAgent, self).__init__()
|
||||
# Handle updates from service
|
||||
self._agent_id = 'hnv_%s' % platform.node()
|
||||
self._neutron_client = neutron_client.NeutronAPIClient()
|
||||
|
||||
def _get_agent_configurations(self):
|
||||
return {
|
||||
'logical_network': CONF.HNV.logical_network,
|
||||
'vswitch_mappings': self._physical_network_mappings,
|
||||
'devices': 1,
|
||||
'l2_population': False,
|
||||
'tunnel_types': [],
|
||||
'bridge_mappings': {},
|
||||
'enable_distributed_routing': False,
|
||||
}
|
||||
|
||||
def _provision_network(self, port_id, net_uuid, network_type,
|
||||
physical_network, segmentation_id):
|
||||
"""Provision the network with the received information."""
|
||||
LOG.info(_LI("Provisioning network %s"), net_uuid)
|
||||
|
||||
vswitch_name = self._get_vswitch_name(network_type, physical_network)
|
||||
vswitch_map = {
|
||||
'network_type': network_type,
|
||||
'vswitch_name': vswitch_name,
|
||||
'ports': [],
|
||||
'vlan_id': segmentation_id}
|
||||
self._network_vswitch_map[net_uuid] = vswitch_map
|
||||
|
||||
def _port_bound(self, port_id, network_id, network_type, physical_network,
|
||||
segmentation_id):
|
||||
"""Bind the port to the recived network."""
|
||||
super(HNVAgent, self)._port_bound(port_id, network_id, network_type,
|
||||
physical_network, segmentation_id)
|
||||
LOG.debug("Getting the profile id for the current port.")
|
||||
profile_id = self._neutron_client.get_port_profile_id(port_id)
|
||||
|
||||
LOG.debug("Trying to set port profile id %r for the current port %r.",
|
||||
profile_id, port_id)
|
||||
self._utils.set_vswitch_port_profile_id(
|
||||
switch_port_name=port_id,
|
||||
profile_id=profile_id,
|
||||
profile_data=h_const.PROFILE_DATA,
|
||||
profile_name=h_const.PROFILE_NAME,
|
||||
net_cfg_instance_id=h_const.NET_CFG_INSTANCE_ID,
|
||||
cdn_label_id=h_const.CDN_LABEL_ID,
|
||||
cdn_label_string=h_const.CDN_LABEL_STRING,
|
||||
vendor_id=h_const.VENDOR_ID,
|
||||
vendor_name=h_const.VENDOR_NAME)
|
||||
|
||||
@_port_synchronized
|
||||
def _treat_vif_port(self, port_id, network_id, network_type,
|
||||
physical_network, segmentation_id,
|
||||
admin_state_up):
|
||||
if admin_state_up:
|
||||
self._port_bound(port_id, network_id, network_type,
|
||||
physical_network, segmentation_id)
|
||||
else:
|
||||
self._port_unbound(port_id)
|
||||
|
||||
|
||||
def main():
|
||||
"""The entry point for the HNV Agent."""
|
||||
neutron_config.register_agent_state_opts_helper(cfg.CONF)
|
||||
common_config.init(sys.argv[1:])
|
||||
neutron_config.setup_logging()
|
||||
|
||||
hnv_agent = HNVAgent()
|
||||
|
||||
# Start everything.
|
||||
LOG.info(_LI("Agent initialized successfully, now running... "))
|
||||
hnv_agent.daemon_loop()
|
|
@ -98,6 +98,14 @@ NEUTRON_OPTS = [
|
|||
help='auth strategy for connecting to neutron in admin context')
|
||||
]
|
||||
|
||||
HNV_OPTS = [
|
||||
cfg.StrOpt(
|
||||
"logical_network", default=None,
|
||||
help=("Logical network to use as a medium for tenant network "
|
||||
"traffic.")),
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(HYPERV_AGENT_OPTS, "AGENT")
|
||||
cfg.CONF.register_opts(NVGRE_OPTS, "NVGRE")
|
||||
cfg.CONF.register_opts(NEUTRON_OPTS, 'neutron')
|
||||
cfg.CONF.register_opts(HNV_OPTS, "HNV")
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
# Topic for tunnel notifications between the plugin and agent
|
||||
AGENT_TOPIC = 'q-agent-notifier'
|
||||
AGENT_TYPE_HYPERV = 'HyperV agent'
|
||||
AGENT_TYPE_HNV = "HNV agent"
|
||||
VIF_TYPE_HYPERV = 'hyperv'
|
||||
|
||||
TUNNEL = 'tunnel'
|
||||
|
@ -32,3 +33,11 @@ TYPE_VLAN = 'vlan'
|
|||
TYPE_NVGRE = 'gre'
|
||||
|
||||
IPV4_DEFAULT = '0.0.0.0'
|
||||
|
||||
NET_CFG_INSTANCE_ID = "{00000000-0000-0000-0000-000000000000}"
|
||||
CDN_LABEL_STRING = "OpenStackHyperVCDN"
|
||||
CDN_LABEL_ID = 1111
|
||||
PROFILE_NAME = "OpenStackProfile"
|
||||
VENDOR_ID = "{00000000-0000-0000-0000-000000000000}"
|
||||
VENDOR_NAME = "NetworkController"
|
||||
PROFILE_DATA = 1
|
||||
|
|
|
@ -21,6 +21,7 @@ from hyperv.common.i18n import _LW, _LE # noqa
|
|||
from hyperv.neutron import constants
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.import_group('neutron', 'hyperv.neutron.config')
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -103,3 +104,13 @@ class NeutronAPIClient(object):
|
|||
except Exception as ex:
|
||||
LOG.error(_LE("Exception caught: %s"), ex)
|
||||
return []
|
||||
|
||||
def get_port_profile_id(self, port_id):
|
||||
try:
|
||||
port = self._client.show_port(port_id)
|
||||
return "{%s}" % (port["port"]["binding:vif_details"]
|
||||
["port_profile_id"])
|
||||
except Exception:
|
||||
LOG.exception(_LE("Failed to retrieve profile id for port %s."),
|
||||
port_id)
|
||||
return {}
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
# 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 Neutron HNV L2 Agent.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from hyperv.neutron.agent import hnv_neutron_agent as hnv_agent
|
||||
from hyperv.neutron import constants
|
||||
from hyperv.tests import base as test_base
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TestHNVAgent(test_base.HyperVBaseTestCase):
|
||||
|
||||
@mock.patch.object(hnv_agent.HNVAgent, "_setup")
|
||||
@mock.patch.object(hnv_agent.HNVAgent, "_setup_rpc")
|
||||
@mock.patch.object(hnv_agent.HNVAgent, "_set_agent_state")
|
||||
def _get_agent(self, mock_set_agent_state, mock_setup_rpc, mock_setup):
|
||||
return hnv_agent.HNVAgent()
|
||||
|
||||
def setUp(self):
|
||||
super(TestHNVAgent, self).setUp()
|
||||
|
||||
self.agent = self._get_agent()
|
||||
self.agent._neutron_client = mock.Mock()
|
||||
|
||||
def test_get_agent_configurations(self):
|
||||
self.config(logical_network=mock.sentinel.logical_network,
|
||||
group="HNV")
|
||||
self.agent._physical_network_mappings = mock.sentinel.mappings
|
||||
|
||||
agent_configurations = self.agent._get_agent_configurations()
|
||||
|
||||
expected_keys = ["logical_network", "vswitch_mappings",
|
||||
"devices", "l2_population", "tunnel_types",
|
||||
"bridge_mappings", "enable_distributed_routing"]
|
||||
self.assertEqual(sorted(expected_keys),
|
||||
sorted(agent_configurations.keys()))
|
||||
self.assertEqual(mock.sentinel.mappings,
|
||||
agent_configurations["vswitch_mappings"])
|
||||
self.assertEqual(mock.sentinel.logical_network,
|
||||
agent_configurations["logical_network"])
|
||||
|
||||
@mock.patch.object(hnv_agent.HNVAgent, "_get_vswitch_name")
|
||||
def test_provision_network(self, mock_get_vswitch_name):
|
||||
self.agent._provision_network(mock.sentinel.port_id,
|
||||
mock.sentinel.net_uuid,
|
||||
mock.sentinel.network_type,
|
||||
mock.sentinel.physical_network,
|
||||
mock.sentinel.segmentation_id)
|
||||
|
||||
mock_get_vswitch_name.assert_called_once_with(
|
||||
mock.sentinel.network_type,
|
||||
mock.sentinel.physical_network)
|
||||
|
||||
vswitch_map = self.agent._network_vswitch_map[mock.sentinel.net_uuid]
|
||||
self.assertEqual(mock.sentinel.network_type,
|
||||
vswitch_map['network_type'])
|
||||
self.assertEqual(mock_get_vswitch_name.return_value,
|
||||
vswitch_map['vswitch_name'])
|
||||
self.assertEqual(mock.sentinel.segmentation_id,
|
||||
vswitch_map['vlan_id'])
|
||||
|
||||
@mock.patch.object(hnv_agent.hyperv_base.Layer2Agent, '_port_bound')
|
||||
def test_port_bound(self, mock_super_port_bound):
|
||||
self.agent._port_bound(
|
||||
mock.sentinel.port_id, mock.sentinel.network_id,
|
||||
mock.sentinel.network_type, mock.sentinel.physical_network,
|
||||
mock.sentinel.segmentation_id)
|
||||
|
||||
mock_super_port_bound.assert_called_once_with(
|
||||
mock.sentinel.port_id, mock.sentinel.network_id,
|
||||
mock.sentinel.network_type, mock.sentinel.physical_network,
|
||||
mock.sentinel.segmentation_id)
|
||||
mock_neutron_client = self.agent._neutron_client
|
||||
mock_neutron_client.get_port_profile_id.assert_called_once_with(
|
||||
mock.sentinel.port_id)
|
||||
self.agent._utils.set_vswitch_port_profile_id.assert_called_once_with(
|
||||
switch_port_name=mock.sentinel.port_id,
|
||||
profile_id=mock_neutron_client.get_port_profile_id.return_value,
|
||||
profile_data=constants.PROFILE_DATA,
|
||||
profile_name=constants.PROFILE_NAME,
|
||||
net_cfg_instance_id=constants.NET_CFG_INSTANCE_ID,
|
||||
cdn_label_id=constants.CDN_LABEL_ID,
|
||||
cdn_label_string=constants.CDN_LABEL_STRING,
|
||||
vendor_id=constants.VENDOR_ID,
|
||||
vendor_name=constants.VENDOR_NAME)
|
||||
|
||||
@mock.patch.object(hnv_agent.HNVAgent, '_port_bound')
|
||||
def test_treat_vif_port_state_up(self, mock_port_bound):
|
||||
self.agent._treat_vif_port(
|
||||
mock.sentinel.port_id, mock.sentinel.network_id,
|
||||
mock.sentinel.network_type, mock.sentinel.physical_network,
|
||||
mock.sentinel.segmentation_id, True)
|
||||
|
||||
mock_port_bound.assert_called_once_with(
|
||||
mock.sentinel.port_id, mock.sentinel.network_id,
|
||||
mock.sentinel.network_type, mock.sentinel.physical_network,
|
||||
mock.sentinel.segmentation_id)
|
||||
|
||||
@mock.patch.object(hnv_agent.HNVAgent, '_port_unbound')
|
||||
def test_treat_vif_port_state_down(self, mock_port_unbound):
|
||||
self.agent._treat_vif_port(
|
||||
mock.sentinel.port_id, mock.sentinel.network_id,
|
||||
mock.sentinel.network_type, mock.sentinel.physical_network,
|
||||
mock.sentinel.segmentation_id, False)
|
||||
|
||||
mock_port_unbound.assert_called_once_with(mock.sentinel.port_id)
|
||||
|
||||
|
||||
class TestMain(test_base.BaseTestCase):
|
||||
|
||||
@mock.patch.object(hnv_agent, 'HNVAgent')
|
||||
@mock.patch.object(hnv_agent, 'common_config')
|
||||
@mock.patch.object(hnv_agent, 'neutron_config')
|
||||
def test_main(self, mock_config, mock_common_config, mock_hnv_agent):
|
||||
hnv_agent.main()
|
||||
|
||||
mock_config.register_agent_state_opts_helper.assert_called_once_with(
|
||||
CONF)
|
||||
mock_common_config.init.assert_called_once_with(sys.argv[1:])
|
||||
mock_config.setup_logging.assert_called_once_with()
|
||||
mock_hnv_agent.assert_called_once_with()
|
||||
mock_hnv_agent.return_value.daemon_loop.assert_called_once_with()
|
|
@ -150,3 +150,25 @@ class TestNeutronClient(base.BaseTestCase):
|
|||
self._neutron._client.list_ports.side_effect = Exception("Fail")
|
||||
actual = self._neutron.get_network_ports()
|
||||
self.assertEqual([], actual)
|
||||
|
||||
def test_get_port_profile_id(self):
|
||||
fake_profile_id = 'fake_profile_id'
|
||||
self._neutron._client.show_port.return_value = {
|
||||
'port': {
|
||||
'binding:vif_details': {'port_profile_id': fake_profile_id}
|
||||
}
|
||||
}
|
||||
|
||||
actual = self._neutron.get_port_profile_id(mock.sentinel.port_id)
|
||||
|
||||
self.assertEqual('{%s}' % fake_profile_id, actual)
|
||||
self._neutron._client.show_port.assert_called_once_with(
|
||||
mock.sentinel.port_id)
|
||||
|
||||
def test_get_port_profile_id_failed(self):
|
||||
self._neutron._client.show_port.side_effect = Exception("Fail")
|
||||
actual = self._neutron.get_port_profile_id(mock.sentinel.port_id)
|
||||
|
||||
self.assertEqual({}, actual)
|
||||
self._neutron._client.show_port.assert_called_once_with(
|
||||
mock.sentinel.port_id)
|
||||
|
|
|
@ -28,6 +28,7 @@ packages =
|
|||
[entry_points]
|
||||
console_scripts =
|
||||
neutron-hyperv-agent = hyperv.neutron.agent.hyperv_neutron_agent:main
|
||||
neutron-hnv-agent = hyperv.neutron.agent.hnv_neutron_agent:main
|
||||
|
||||
neutron.qos.agent_drivers =
|
||||
hyperv = hyperv.neutron.qos.qos_driver:QosHyperVAgentDriver
|
||||
|
|
Loading…
Reference in New Issue