summaryrefslogtreecommitdiffstats
path: root/networking-odl/networking_odl/tests/unit/ml2/test_networking_topology.py
diff options
context:
space:
mode:
Diffstat (limited to 'networking-odl/networking_odl/tests/unit/ml2/test_networking_topology.py')
-rw-r--r--networking-odl/networking_odl/tests/unit/ml2/test_networking_topology.py475
1 files changed, 475 insertions, 0 deletions
diff --git a/networking-odl/networking_odl/tests/unit/ml2/test_networking_topology.py b/networking-odl/networking_odl/tests/unit/ml2/test_networking_topology.py
new file mode 100644
index 0000000..fb83a7b
--- /dev/null
+++ b/networking-odl/networking_odl/tests/unit/ml2/test_networking_topology.py
@@ -0,0 +1,475 @@
+# Copyright (c) 2015-2016 OpenStack Foundation
+# 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 os import path
+
+import mock
+from oslo_log import log
+from oslo_serialization import jsonutils
+import requests
+
+from neutron.extensions import portbindings
+from neutron.plugins.common import constants
+from neutron.plugins.ml2 import driver_api
+from neutron.plugins.ml2 import driver_context
+from neutron_lib import constants as n_constants
+
+from networking_odl.common import cache
+from networking_odl.ml2 import mech_driver
+from networking_odl.ml2 import mech_driver_v2
+from networking_odl.ml2 import network_topology
+from networking_odl.tests import base
+
+
+LOG = log.getLogger(__name__)
+
+
+class TestNetworkTopologyManager(base.DietTestCase):
+
+ # pylint: disable=protected-access
+
+ # given valid and invalid segments
+ valid_segment = {
+ driver_api.ID: 'API_ID',
+ driver_api.NETWORK_TYPE: constants.TYPE_LOCAL,
+ driver_api.SEGMENTATION_ID: 'API_SEGMENTATION_ID',
+ driver_api.PHYSICAL_NETWORK: 'API_PHYSICAL_NETWORK'}
+
+ invalid_segment = {
+ driver_api.ID: 'API_ID',
+ driver_api.NETWORK_TYPE: constants.TYPE_NONE,
+ driver_api.SEGMENTATION_ID: 'API_SEGMENTATION_ID',
+ driver_api.PHYSICAL_NETWORK: 'API_PHYSICAL_NETWORK'}
+
+ segments_to_bind = [valid_segment, invalid_segment]
+
+ def setUp(self):
+ super(TestNetworkTopologyManager, self).setUp()
+ self.patch(network_topology.LOG, 'isEnabledFor', lambda level: True)
+ # patch given configuration
+ self.cfg = mocked_cfg = self.patch(network_topology.client, 'cfg')
+ mocked_cfg.CONF.ml2_odl.url =\
+ 'http://localhost:8181/controller/nb/v2/neutron'
+ mocked_cfg.CONF.ml2_odl.username = 'admin'
+ mocked_cfg.CONF.ml2_odl.password = 'admin'
+ mocked_cfg.CONF.ml2_odl.timeout = 5
+
+ @mock.patch.object(cache, 'LOG')
+ @mock.patch.object(network_topology, 'LOG')
+ def test_fetch_elements_by_host_with_no_entry(
+ self, network_topology_logger, cache_logger):
+ given_client = self.mock_client('ovs_topology.json')
+ self.mock_get_addresses_by_name(['127.0.0.1', '192.168.0.1'])
+ given_network_topology = network_topology.NetworkTopologyManager(
+ client=given_client)
+
+ try:
+ next(given_network_topology._fetch_elements_by_host(
+ 'some_host_name'))
+ except ValueError as error:
+ cache_logger.warning.assert_called_once_with(
+ 'Error fetching values for keys: %r',
+ "'some_host_name', '127.0.0.1', '192.168.0.1'",
+ exc_info=(ValueError, error, mock.ANY))
+ network_topology_logger.exception.assert_called_once_with(
+ 'No such network topology elements for given host '
+ '%(host_name)r and given IPs: %(ip_addresses)s.',
+ {'ip_addresses': '127.0.0.1, 192.168.0.1',
+ 'host_name': 'some_host_name'})
+ else:
+ self.fail('Expected ValueError being raised.')
+
+ def test_fetch_element_with_ovs_entry(self):
+ given_client = self.mock_client('ovs_topology.json')
+ self.mock_get_addresses_by_name(['127.0.0.1', '10.237.214.247'])
+ given_network_topology = network_topology.NetworkTopologyManager(
+ client=given_client)
+
+ elements = given_network_topology._fetch_elements_by_host(
+ 'some_host_name.')
+
+ self.assertEqual([
+ {'class':
+ 'networking_odl.ml2.ovsdb_topology.OvsdbNetworkTopologyElement',
+ 'has_datapath_type_netdev': False,
+ 'host_addresses': ['10.237.214.247'],
+ 'support_vhost_user': False,
+ 'uuid': 'c4ad780f-8f91-4fa4-804e-dd16beb191e2',
+ 'valid_vif_types': [portbindings.VIF_TYPE_OVS]}],
+ [e.to_dict() for e in elements])
+
+ def test_fetch_elements_with_vhost_user_entry(self):
+ given_client = self.mock_client('vhostuser_topology.json')
+ self.mock_get_addresses_by_name(['127.0.0.1', '192.168.66.1'])
+ given_network_topology = network_topology.NetworkTopologyManager(
+ client=given_client)
+
+ elements = given_network_topology._fetch_elements_by_host(
+ 'some_host_name.')
+
+ self.assertEqual([
+ {'class':
+ 'networking_odl.ml2.ovsdb_topology.OvsdbNetworkTopologyElement',
+ 'has_datapath_type_netdev': True,
+ 'host_addresses': ['192.168.66.1'],
+ 'support_vhost_user': True,
+ 'uuid': 'c805d82d-a5d8-419d-bc89-6e3713ff9f6c',
+ 'valid_vif_types': [portbindings.VIF_TYPE_VHOST_USER,
+ portbindings.VIF_TYPE_OVS],
+ 'port_prefix': 'vhu',
+ 'vhostuser_socket_dir': '/var/run/openvswitch'}],
+ [e.to_dict() for e in elements])
+
+ def mock_get_addresses_by_name(self, ips):
+ utils = self.patch(
+ network_topology, 'utils',
+ mock.Mock(
+ get_addresses_by_name=mock.Mock(return_value=tuple(ips))))
+ return utils.get_addresses_by_name
+
+ def mock_client(self, topology_name=None):
+
+ mocked_client = mock.NonCallableMock(
+ specs=network_topology.NetworkTopologyClient)
+
+ if topology_name:
+ cached_file_path = path.join(path.dirname(__file__), topology_name)
+
+ with open(cached_file_path, 'rt') as fd:
+ topology = jsonutils.loads(str(fd.read()), encoding='utf-8')
+
+ mocked_client.get().json.return_value = topology
+
+ return mocked_client
+
+ def test_bind_port_from_mech_driver_with_ovs(self):
+
+ given_client = self.mock_client('ovs_topology.json')
+ self.mock_get_addresses_by_name(['127.0.0.1', '10.237.214.247'])
+ given_network_topology = network_topology.NetworkTopologyManager(
+ vif_details={'some': 'detail'},
+ client=given_client)
+ self.patch(
+ network_topology, 'NetworkTopologyManager',
+ return_value=given_network_topology)
+
+ given_driver = mech_driver.OpenDaylightMechanismDriver()
+ given_driver.odl_drv = mech_driver.OpenDaylightDriver()
+ given_port_context = self.given_port_context()
+
+ # when port is bound
+ given_driver.bind_port(given_port_context)
+
+ # then context binding is setup with returned vif_type and valid
+ # segment api ID
+ given_port_context.set_binding.assert_called_once_with(
+ self.valid_segment[driver_api.ID], portbindings.VIF_TYPE_OVS,
+ {'some': 'detail'}, status=n_constants.PORT_STATUS_ACTIVE)
+
+ def test_bind_port_from_mech_driver_with_vhostuser(self):
+
+ given_client = self.mock_client('vhostuser_topology.json')
+ self.mock_get_addresses_by_name(['127.0.0.1', '192.168.66.1'])
+ given_network_topology = network_topology.NetworkTopologyManager(
+ vif_details={'some': 'detail'},
+ client=given_client)
+ self.patch(
+ network_topology, 'NetworkTopologyManager',
+ return_value=given_network_topology)
+
+ given_driver = mech_driver.OpenDaylightMechanismDriver()
+ given_driver.odl_drv = mech_driver.OpenDaylightDriver()
+ given_port_context = self.given_port_context()
+
+ # when port is bound
+ given_driver.bind_port(given_port_context)
+
+ expected_vif_details = {
+ 'vhostuser_socket': '/var/run/openvswitch/vhuCURRENT_CON',
+ 'vhostuser_ovs_plug': True,
+ 'some': 'detail',
+ 'vhostuser_mode': 'client'}
+
+ # then context binding is setup with returned vif_type and valid
+ # segment api ID
+ given_port_context.set_binding.assert_called_once_with(
+ self.valid_segment[driver_api.ID],
+ portbindings.VIF_TYPE_VHOST_USER,
+ expected_vif_details, status=n_constants.PORT_STATUS_ACTIVE)
+
+ def test_bind_port_from_mech_driver_v2_with_ovs(self):
+ given_client = self.mock_client('ovs_topology.json')
+ self.mock_get_addresses_by_name(['127.0.0.1', '10.237.214.247'])
+ given_network_topology = network_topology.NetworkTopologyManager(
+ vif_details={'some': 'detail'},
+ client=given_client)
+ self.patch(
+ network_topology, 'NetworkTopologyManager',
+ return_value=given_network_topology)
+
+ given_driver = mech_driver_v2.OpenDaylightMechanismDriver()
+ given_port_context = self.given_port_context()
+
+ given_driver.initialize()
+ # when port is bound
+ given_driver.bind_port(given_port_context)
+
+ # then context binding is setup with returned vif_type and valid
+ # segment api ID
+ given_port_context.set_binding.assert_called_once_with(
+ self.valid_segment[driver_api.ID], portbindings.VIF_TYPE_OVS,
+ {'some': 'detail'}, status=n_constants.PORT_STATUS_ACTIVE)
+
+ def test_bind_port_from_mech_driver_v2_with_vhostuser(self):
+ given_client = self.mock_client('vhostuser_topology.json')
+ self.mock_get_addresses_by_name(['127.0.0.1', '192.168.66.1'])
+ given_network_topology = network_topology.NetworkTopologyManager(
+ vif_details={'some': 'detail'},
+ client=given_client)
+ self.patch(
+ network_topology, 'NetworkTopologyManager',
+ return_value=given_network_topology)
+
+ given_driver = mech_driver_v2.OpenDaylightMechanismDriver()
+ given_driver._network_topology = given_network_topology
+ given_port_context = self.given_port_context()
+
+ given_driver.initialize()
+ # when port is bound
+ given_driver.bind_port(given_port_context)
+
+ expected_vif_details = {
+ 'vhostuser_socket': '/var/run/openvswitch/vhuCURRENT_CON',
+ 'vhostuser_ovs_plug': True,
+ 'some': 'detail',
+ 'vhostuser_mode': 'client'}
+
+ # then context binding is setup with returned vif_type and valid
+ # segment api ID
+ given_port_context.set_binding.assert_called_once_with(
+ self.valid_segment[driver_api.ID],
+ portbindings.VIF_TYPE_VHOST_USER,
+ expected_vif_details, status=n_constants.PORT_STATUS_ACTIVE)
+
+ def test_bind_port_with_vif_type_ovs(self):
+ given_topology = self._mock_network_topology(
+ 'ovs_topology.json', vif_details={'much': 'details'})
+ given_port_context = self.given_port_context()
+
+ # when port is bound
+ given_topology.bind_port(given_port_context)
+
+ # then context binding is setup wit returned vif_type and valid
+ # segment api ID
+ given_port_context.set_binding.assert_called_once_with(
+ self.valid_segment[driver_api.ID], portbindings.VIF_TYPE_OVS,
+ {'much': 'details'}, status=n_constants.PORT_STATUS_ACTIVE)
+
+ def test_bind_port_with_vif_type_vhost_user(self):
+ given_topology = self._mock_network_topology(
+ 'vhostuser_topology.json', vif_details={'much': 'details'})
+ given_port_context = self.given_port_context()
+
+ # when port is bound
+ given_topology.bind_port(given_port_context)
+
+ # then context binding is setup wit returned vif_type and valid
+ # segment api ID
+ given_port_context.set_binding.assert_called_once_with(
+ self.valid_segment[driver_api.ID],
+ portbindings.VIF_TYPE_VHOST_USER,
+ {'vhostuser_socket': '/var/run/openvswitch/vhuCURRENT_CON',
+ 'vhostuser_ovs_plug': True, 'vhostuser_mode': 'client',
+ 'much': 'details'},
+ status=n_constants.PORT_STATUS_ACTIVE)
+
+ @mock.patch.object(network_topology, 'LOG')
+ def test_bind_port_without_valid_segment(self, logger):
+ given_topology = self._mock_network_topology('ovs_topology.json')
+ given_port_context = self.given_port_context(
+ given_segments=[self.invalid_segment])
+
+ # when port is bound
+ given_topology.bind_port(given_port_context)
+
+ self.assertFalse(given_port_context.set_binding.called)
+ logger.exception.assert_called_once_with(
+ 'Network topology element has failed binding port:\n%(element)s',
+ {'element': mock.ANY})
+ logger.error.assert_called_once_with(
+ 'Unable to bind port element for given host and valid VIF types:\n'
+ '\thostname: %(host_name)s\n'
+ '\tvalid VIF types: %(valid_vif_types)s',
+ {'host_name': 'some_host', 'valid_vif_types': 'vhostuser, ovs'})
+
+ def _mock_network_topology(self, given_topology, vif_details=None):
+ self.mock_get_addresses_by_name(
+ ['127.0.0.1', '10.237.214.247', '192.168.66.1'])
+ return network_topology.NetworkTopologyManager(
+ client=self.mock_client(given_topology),
+ vif_details=vif_details)
+
+ def given_port_context(self, given_segments=None):
+ # given NetworkContext
+ network = mock.MagicMock(spec=driver_api.NetworkContext)
+
+ if given_segments is None:
+ given_segments = self.segments_to_bind
+
+ # given port context
+ return mock.MagicMock(
+ spec=driver_context.PortContext,
+ current={'id': 'CURRENT_CONTEXT_ID'},
+ host='some_host',
+ segments_to_bind=given_segments,
+ network=network,
+ _new_bound_segment=self.valid_segment)
+
+ NETOWORK_TOPOLOGY_URL =\
+ 'http://localhost:8181/'\
+ 'restconf/operational/network-topology:network-topology/'
+
+ def mock_request_network_topology(self, file_name):
+ cached_file_path = path.join(
+ path.dirname(__file__), file_name + '.json')
+
+ if path.isfile(cached_file_path):
+ LOG.debug('Loading topology from file: %r', cached_file_path)
+ with open(cached_file_path, 'rt') as fd:
+ topology = jsonutils.loads(str(fd.read()), encoding='utf-8')
+ else:
+ LOG.debug(
+ 'Getting topology from ODL: %r', self.NETOWORK_TOPOLOGY_URL)
+ request = requests.get(
+ self.NETOWORK_TOPOLOGY_URL, auth=('admin', 'admin'),
+ headers={'Content-Type': 'application/json'})
+ request.raise_for_status()
+
+ with open(cached_file_path, 'wt') as fd:
+ LOG.debug('Saving topology to file: %r', cached_file_path)
+ topology = request.json()
+ jsonutils.dump(
+ topology, fd, sort_keys=True, indent=4,
+ separators=(',', ': '))
+
+ mocked_request = self.patch(
+ mech_driver.odl_client.requests, 'request',
+ return_value=mock.MagicMock(
+ spec=requests.Response,
+ json=mock.MagicMock(return_value=topology)))
+
+ return mocked_request
+
+
+class TestNetworkTopologyClient(base.DietTestCase):
+
+ given_host = 'given.host'
+ given_port = 1234
+ given_url_with_port = 'http://{}:{}/'.format(
+ given_host, given_port)
+ given_url_without_port = 'http://{}/'.format(given_host)
+ given_username = 'GIVEN_USERNAME'
+ given_password = 'GIVEN_PASSWORD'
+ given_timeout = 20
+
+ def given_client(
+ self, url=None, username=None, password=None, timeout=None):
+ return network_topology.NetworkTopologyClient(
+ url=url or self.given_url_with_port,
+ username=username or self.given_username,
+ password=password or self.given_password,
+ timeout=timeout or self.given_timeout)
+
+ def test_constructor(self):
+ # When client is created
+ rest_client = network_topology.NetworkTopologyClient(
+ url=self.given_url_with_port,
+ username=self.given_username,
+ password=self.given_password,
+ timeout=self.given_timeout)
+
+ self.assertEqual(
+ self.given_url_with_port +
+ 'restconf/operational/network-topology:network-topology',
+ rest_client.url)
+ self.assertEqual(
+ (self.given_username, self.given_password), rest_client.auth)
+ self.assertEqual(self.given_timeout, rest_client.timeout)
+
+ def test_request_with_port(self):
+ # Given rest client and used 'requests' module
+ given_client = self.given_client()
+ mocked_requests_module = self.mocked_requests()
+
+ # When a request is performed
+ result = given_client.request(
+ 'GIVEN_METHOD', 'given/path', 'GIVEN_DATA')
+
+ # Then request method is called
+ mocked_requests_module.request.assert_called_once_with(
+ 'GIVEN_METHOD',
+ url='http://given.host:1234/restconf/operational/' +
+ 'network-topology:network-topology/given/path',
+ auth=(self.given_username, self.given_password),
+ data='GIVEN_DATA', headers={'Content-Type': 'application/json'},
+ timeout=self.given_timeout)
+
+ # Then request method result is returned
+ self.assertIs(mocked_requests_module.request.return_value, result)
+
+ def test_request_without_port(self):
+ # Given rest client and used 'requests' module
+ given_client = self.given_client(url=self.given_url_without_port)
+ mocked_requests_module = self.mocked_requests()
+
+ # When a request is performed
+ result = given_client.request(
+ 'GIVEN_METHOD', 'given/path', 'GIVEN_DATA')
+
+ # Then request method is called
+ mocked_requests_module.request.assert_called_once_with(
+ 'GIVEN_METHOD',
+ url='http://given.host/restconf/operational/' +
+ 'network-topology:network-topology/given/path',
+ auth=(self.given_username, self.given_password),
+ data='GIVEN_DATA', headers={'Content-Type': 'application/json'},
+ timeout=self.given_timeout)
+
+ # Then request method result is returned
+ self.assertIs(mocked_requests_module.request.return_value, result)
+
+ def test_get(self):
+ # Given rest client and used 'requests' module
+ given_client = self.given_client()
+ mocked_requests_module = self.mocked_requests()
+
+ # When a request is performed
+ result = given_client.get('given/path', 'GIVEN_DATA')
+
+ # Then request method is called
+ mocked_requests_module.request.assert_called_once_with(
+ 'get',
+ url='http://given.host:1234/restconf/operational/' +
+ 'network-topology:network-topology/given/path',
+ auth=(self.given_username, self.given_password),
+ data='GIVEN_DATA', headers={'Content-Type': 'application/json'},
+ timeout=self.given_timeout)
+
+ # Then request method result is returned
+ self.assertIs(mocked_requests_module.request.return_value, result)
+
+ def mocked_requests(self):
+ return self.patch(network_topology.client, 'requests')