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:
Alexandru Coman 2017-02-15 17:06:08 +02:00 committed by Claudiu Belu
parent 9004336aa9
commit 86fa652c0e
7 changed files with 310 additions and 0 deletions

View File

@ -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()

View File

@ -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")

View File

@ -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

View File

@ -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 {}

View File

@ -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()

View File

@ -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)

View File

@ -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