# Copyright (c) 2016 Hewlett Packard Enterprise Development Company, L.P. # # 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. import copy from unittest import mock import netaddr from neutron_lib.api.definitions import ip_allocation as ipalloc_apidef from neutron_lib.api.definitions import l2_adjacency as l2adj_apidef from neutron_lib.api.definitions import port as port_apidef from neutron_lib.api.definitions import portbindings from neutron_lib.api.definitions import segment as seg_apidef from neutron_lib.callbacks import events from neutron_lib.callbacks import exceptions from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants from neutron_lib import context from neutron_lib import exceptions as n_exc from neutron_lib.exceptions import placement as placement_exc from neutron_lib.plugins import directory from novaclient import exceptions as nova_exc from oslo_config import cfg from oslo_utils import uuidutils import webob.exc from neutron.conf.plugins.ml2 import config as ml2_config from neutron.conf.plugins.ml2.drivers import driver_type from neutron.db import agents_db from neutron.db import agentschedulers_db from neutron.db import db_base_plugin_v2 from neutron.db import portbindings_db from neutron.db import segments_db from neutron.extensions import segment as ext_segment from neutron.extensions import standardattrdescription as ext_stddesc from neutron.objects import network from neutron.services.segments import db from neutron.services.segments import exceptions as segment_exc from neutron.services.segments import plugin as seg_plugin from neutron.tests.common import helpers from neutron.tests.unit.db import test_db_base_plugin_v2 SERVICE_PLUGIN_KLASS = 'neutron.services.segments.plugin.Plugin' TEST_PLUGIN_KLASS = ( 'neutron.tests.unit.extensions.test_segment.SegmentTestPlugin') DHCP_HOSTA = 'dhcp-host-a' DHCP_HOSTB = 'dhcp-host-b' HTTP_NOT_FOUND = 404 class SegmentTestExtensionManager(object): def get_resources(self): ext_segment.Segment().update_attributes_map( {ext_segment.SEGMENTS: ext_stddesc.DESCRIPTION_BODY}) return ext_segment.Segment.get_resources() def get_actions(self): return [] def get_request_extensions(self): return [] class SegmentTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase): def setUp(self, plugin=None): # Remove MissingAuthPlugin exception from logs self.patch_notifier = mock.patch( 'neutron.notifiers.batch_notifier.BatchNotifier._notify') self.patch_notifier.start() if not plugin: plugin = TEST_PLUGIN_KLASS service_plugins = {'segments_plugin_name': SERVICE_PLUGIN_KLASS} cfg.CONF.set_override('service_plugins', [SERVICE_PLUGIN_KLASS]) ext_mgr = SegmentTestExtensionManager() super(SegmentTestCase, self).setUp(plugin=plugin, ext_mgr=ext_mgr, service_plugins=service_plugins) def _create_segment(self, fmt, expected_res_status=None, **kwargs): segment = {'segment': {}} for k, v in kwargs.items(): segment['segment'][k] = None if v is None else str(v) segment_req = self.new_create_request( 'segments', segment, fmt) segment_res = segment_req.get_response(self.ext_api) if expected_res_status: self.assertEqual(segment_res.status_int, expected_res_status) return segment_res def _make_segment(self, fmt, **kwargs): res = self._create_segment(fmt, **kwargs) if res.status_int >= webob.exc.HTTPClientError.code: res.charset = 'utf8' raise webob.exc.HTTPClientError( code=res.status_int, explanation=str(res)) return self.deserialize(fmt, res) def segment(self, **kwargs): kwargs.setdefault('network_type', 'net_type') return self._make_segment( self.fmt, tenant_id=self._tenant_id, **kwargs) def _test_create_segment(self, expected=None, **kwargs): keys = kwargs.copy() segment = self.segment(**keys) self._validate_resource(segment, keys, 'segment') if expected: self._compare_resource(segment, expected, 'segment') return segment class SegmentTestPlugin(db_base_plugin_v2.NeutronDbPluginV2, portbindings_db.PortBindingMixin, db.SegmentDbMixin): __native_pagination_support = True __native_sorting_support = True supported_extension_aliases = [seg_apidef.ALIAS, portbindings.ALIAS, ipalloc_apidef.ALIAS] def get_plugin_description(self): return "Network Segments" @classmethod def get_plugin_type(cls): return "segments" def create_port(self, context, port): port_dict = super(SegmentTestPlugin, self).create_port(context, port) self._process_portbindings_create_and_update( context, port['port'], port_dict) return port_dict def update_port(self, context, id, port): port_dict = super(SegmentTestPlugin, self).update_port( context, id, port) self._process_portbindings_create_and_update( context, port['port'], port_dict) return port_dict class TestSegmentNameDescription(SegmentTestCase): def setUp(self): super(TestSegmentNameDescription, self).setUp() with self.network() as network: self.network = network['network'] def _test_create_segment(self, expected=None, **kwargs): for d in (kwargs, expected): if d is None: continue d.setdefault('network_id', self.network['id']) d.setdefault('name', None) d.setdefault('description', 'desc') d.setdefault('physical_network', 'phys_net') d.setdefault('network_type', 'net_type') d.setdefault('segmentation_id', 200) return super(TestSegmentNameDescription, self)._test_create_segment( expected, **kwargs) def test_create_segment_no_name(self): self._test_create_segment(expected={}) def test_create_segment_with_name(self): expected_segment = {'name': 'segment_name'} self._test_create_segment(name='segment_name', expected=expected_segment) def test_create_segment_with_description(self): expected_segment = {'description': 'A segment'} self._test_create_segment(description='A segment', expected=expected_segment) def test_update_segment_set_name(self): segment = self._test_create_segment() result = self._update('segments', segment['segment']['id'], {'segment': {'name': 'Segment name'}}, expected_code=webob.exc.HTTPOk.code) self.assertEqual('Segment name', result['segment']['name']) def test_update_segment_set_description(self): segment = self._test_create_segment() result = self._update('segments', segment['segment']['id'], {'segment': {'description': 'Segment desc'}}, expected_code=webob.exc.HTTPOk.code) self.assertEqual('Segment desc', result['segment']['description']) def test_update_segment_set_name_to_none(self): segment = self._test_create_segment( description='A segment', name='segment') result = self._update('segments', segment['segment']['id'], {'segment': {'name': None}}, expected_code=webob.exc.HTTPOk.code) self.assertIsNone(result['segment']['name']) def test_update_segment_set_description_to_none(self): segment = self._test_create_segment( description='A segment', name='segment') self._update('segments', segment['segment']['id'], {'segment': {'description': None}}, expected_code=webob.exc.HTTPBadRequest.code) class TestSegment(SegmentTestCase): def test_create_segment(self): with self.network() as network: network = network['network'] expected_segment = {'network_id': network['id'], 'physical_network': 'phys_net', 'network_type': 'net_type', 'segmentation_id': 200} self._test_create_segment(network_id=network['id'], physical_network='phys_net', segmentation_id=200, expected=expected_segment) def test_create_segment_non_existent_network(self): exc = self.assertRaises(webob.exc.HTTPClientError, self._test_create_segment, network_id=uuidutils.generate_uuid(), physical_network='phys_net', segmentation_id=200) self.assertEqual(HTTP_NOT_FOUND, exc.code) self.assertIn('NetworkNotFound', exc.explanation) def test_create_segment_no_phys_net(self): with self.network() as network: network = network['network'] expected_segment = {'network_id': network['id'], 'physical_network': None, 'network_type': 'net_type', 'segmentation_id': 200} self._test_create_segment(network_id=network['id'], segmentation_id=200, expected=expected_segment) def test_create_segment_no_segmentation_id(self): def _mock_reserve_segmentation_id(rtype, event, trigger, context, segment): if not segment.get('segmentation_id'): segment['segmentation_id'] = 200 with self.network() as network: network = network['network'] registry.subscribe(_mock_reserve_segmentation_id, resources.SEGMENT, events.PRECOMMIT_CREATE) expected_segment = {'network_id': network['id'], 'physical_network': 'phys_net', 'network_type': 'net_type', 'segmentation_id': 200} self._test_create_segment(network_id=network['id'], physical_network='phys_net', expected=expected_segment) def test_create_segment_with_exception_in_core_plugin(self): cxt = context.get_admin_context() with self.network() as network: network = network['network'] with mock.patch.object(registry, 'notify') as notify: notify.side_effect = exceptions.CallbackFailure(errors=Exception) self.assertRaises(webob.exc.HTTPClientError, self.segment, network_id=network['id'], segmentation_id=200) network_segments = segments_db.get_network_segments(cxt, network['id']) self.assertEqual([], network_segments) def test_create_segments_in_certain_order(self): cxt = context.get_admin_context() with self.network() as network: network = network['network'] segment1 = self.segment( network_id=network['id'], segmentation_id=200) segment2 = self.segment( network_id=network['id'], segmentation_id=201) segment3 = self.segment( network_id=network['id'], segmentation_id=202) network_segments = segments_db.get_network_segments(cxt, network['id']) self.assertEqual(segment1['segment']['id'], network_segments[0]['id']) self.assertEqual(segment2['segment']['id'], network_segments[1]['id']) self.assertEqual(segment3['segment']['id'], network_segments[2]['id']) def test_delete_segment(self): with self.network() as network: network = network['network'] self.segment(network_id=network['id'], segmentation_id=200) segment = self.segment(network_id=network['id'], segmentation_id=201) self._delete('segments', segment['segment']['id']) self._show('segments', segment['segment']['id'], expected_code=webob.exc.HTTPNotFound.code) def test_delete_segment_failed_with_subnet_associated(self): with self.network() as network: net = network['network'] segment = self._test_create_segment(network_id=net['id'], segmentation_id=200) segment_id = segment['segment']['id'] with self.subnet(network=network, segment_id=segment_id): self._delete('segments', segment_id, expected_code=webob.exc.HTTPConflict.code) exist_segment = self._show('segments', segment_id) self.assertEqual(segment_id, exist_segment['segment']['id']) def test_get_segment(self): with self.network() as network: network = network['network'] segment = self._test_create_segment(network_id=network['id'], physical_network='phys_net', segmentation_id=200) req = self.new_show_request('segments', segment['segment']['id']) res = self.deserialize(self.fmt, req.get_response(self.ext_api)) self.assertEqual(segment['segment']['id'], res['segment']['id']) def test_list_segments(self): with self.network() as network: network = network['network'] self._test_create_segment(network_id=network['id'], physical_network='phys_net1', segmentation_id=200) self._test_create_segment(network_id=network['id'], physical_network='phys_net2', segmentation_id=201) res = self._list('segments') self.assertEqual(2, len(res['segments'])) def test_list_segments_with_sort(self): with self.network() as network: network = network['network'] s1 = self._test_create_segment(network_id=network['id'], physical_network='phys_net1', segmentation_id=200) s2 = self._test_create_segment(network_id=network['id'], physical_network='phys_net2', segmentation_id=201) self._test_list_with_sort('segment', (s2, s1), [('physical_network', 'desc')], query_params='network_id=%s' % network['id']) def test_list_segments_with_pagination(self): with self.network() as network: network = network['network'] s1 = self._test_create_segment(network_id=network['id'], physical_network='phys_net1', segmentation_id=200) s2 = self._test_create_segment(network_id=network['id'], physical_network='phys_net2', segmentation_id=201) s3 = self._test_create_segment(network_id=network['id'], physical_network='phys_net3', segmentation_id=202) self._test_list_with_pagination( 'segment', (s1, s2, s3), ('physical_network', 'asc'), 2, 2, query_params='network_id=%s' % network['id']) def test_list_segments_with_pagination_reverse(self): with self.network() as network: network = network['network'] s1 = self._test_create_segment(network_id=network['id'], physical_network='phys_net1', segmentation_id=200) s2 = self._test_create_segment(network_id=network['id'], physical_network='phys_net2', segmentation_id=201) s3 = self._test_create_segment(network_id=network['id'], physical_network='phys_net3', segmentation_id=202) self._test_list_with_pagination_reverse( 'segment', (s1, s2, s3), ('physical_network', 'asc'), 2, 2, query_params='network_id=%s' % network['id']) def test_update_segments(self): with self.network() as network: net = network['network'] segment = self._test_create_segment(network_id=net['id'], segmentation_id=200) segment['segment']['segmentation_id'] = '201' self._update('segments', segment['segment']['id'], segment, expected_code=webob.exc.HTTPClientError.code) def test_segment_notification_on_delete_network(self): with mock.patch.object(db, '_delete_segments_for_network') as dsn: db.subscribe() with self.network() as network: network = network['network'] self._delete('networks', network['id']) dsn.assert_called_with(resources.NETWORK, events.PRECOMMIT_DELETE, mock.ANY, context=mock.ANY, network_id=mock.ANY, network=mock.ANY) class TestSegmentML2(SegmentTestCase): def setUp(self): super(TestSegmentML2, self).setUp(plugin='ml2') def test_segment_notification_on_create_network(self): with mock.patch.object(registry, 'notify') as notify: with self.network(): pass notify.assert_any_call(resources.SEGMENT, events.PRECOMMIT_CREATE, context=mock.ANY, segment=mock.ANY, trigger=mock.ANY) class TestSegmentSubnetAssociation(SegmentTestCase): def test_basic_association(self): with self.network() as network: net = network['network'] segment = self._test_create_segment(network_id=net['id'], segmentation_id=200) segment_id = segment['segment']['id'] with self.subnet(network=network, segment_id=segment_id) as subnet: subnet = subnet['subnet'] request = self.new_show_request('subnets', subnet['id']) response = request.get_response(self.api) res = self.deserialize(self.fmt, response) self.assertEqual(segment_id, res['subnet']['segment_id']) def test_association_network_mismatch(self): with self.network() as network1: with self.network() as network2: net = network1['network'] segment = self._test_create_segment(network_id=net['id'], segmentation_id=200) res = self._create_subnet(self.fmt, net_id=network2['network']['id'], tenant_id=network2['network']['tenant_id'], gateway_ip=constants.ATTR_NOT_SPECIFIED, cidr='10.0.0.0/24', segment_id=segment['segment']['id']) self.assertEqual(webob.exc.HTTPBadRequest.code, res.status_int) def test_association_segment_not_found(self): with self.network() as network: net = network['network'] segment_id = uuidutils.generate_uuid() res = self._create_subnet(self.fmt, net_id=net['id'], tenant_id=net['tenant_id'], gateway_ip=constants.ATTR_NOT_SPECIFIED, cidr='10.0.0.0/24', segment_id=segment_id) self.assertEqual(webob.exc.HTTPNotFound.code, res.status_int) def test_only_some_subnets_associated_not_allowed(self): with self.network() as network: with self.subnet(network=network): net = network['network'] segment = self._test_create_segment(network_id=net['id'], segmentation_id=200) res = self._create_subnet(self.fmt, net_id=net['id'], tenant_id=net['tenant_id'], gateway_ip=constants.ATTR_NOT_SPECIFIED, cidr='10.0.1.0/24', segment_id=segment['segment']['id']) self.assertEqual(webob.exc.HTTPBadRequest.code, res.status_int) def test_association_to_dynamic_segment_not_allowed(self): cxt = context.get_admin_context() with self.network() as network: net = network['network'] # Can't create a dynamic segment through the API segment = {segments_db.NETWORK_TYPE: 'phys_net', segments_db.PHYSICAL_NETWORK: 'net_type', segments_db.SEGMENTATION_ID: 200} segments_db.add_network_segment(cxt, network_id=net['id'], segment=segment, is_dynamic=True) res = self._create_subnet(self.fmt, net_id=net['id'], tenant_id=net['tenant_id'], gateway_ip=constants.ATTR_NOT_SPECIFIED, cidr='10.0.0.0/24', segment_id=segment['id']) self.assertEqual(webob.exc.HTTPBadRequest.code, res.status_int) def test_associate_existing_subnet_with_segment(self): with self.network() as network: net = network['network'] segment = self._test_create_segment(network_id=net['id'], physical_network='phys_net', segmentation_id=200)['segment'] with self.subnet(network=network, segment_id=None) as subnet: subnet = subnet['subnet'] data = {'subnet': {'segment_id': segment['id']}} request = self.new_update_request('subnets', data, subnet['id']) response = request.get_response(self.api) res = self.deserialize(self.fmt, response) self.assertEqual(webob.exc.HTTPOk.code, response.status_int) self.assertEqual(res['subnet']['segment_id'], segment['id']) def test_update_subnet_with_current_segment_id(self): with self.network() as network: net = network['network'] segment1 = self._test_create_segment(network_id=net['id'], physical_network='phys_net1', segmentation_id=200)['segment'] self._test_create_segment(network_id=net['id'], physical_network='phys_net2', segmentation_id=200)['segment'] with self.subnet(network=network, segment_id=segment1['id']) as subnet: subnet = subnet['subnet'] data = {'subnet': {'segment_id': segment1['id']}} request = self.new_update_request('subnets', data, subnet['id']) response = request.get_response(self.api) res = self.deserialize(self.fmt, response) self.assertEqual(webob.exc.HTTPOk.code, response.status_int) self.assertEqual(res['subnet']['segment_id'], segment1['id']) def test_associate_existing_subnet_fail_if_multiple_segments(self): with self.network() as network: net = network['network'] segment1 = self._test_create_segment(network_id=net['id'], physical_network='phys_net1', segmentation_id=201)['segment'] self._test_create_segment(network_id=net['id'], physical_network='phys_net2', segmentation_id=202)['segment'] with self.subnet(network=network, segment_id=None) as subnet: subnet = subnet['subnet'] data = {'subnet': {'segment_id': segment1['id']}} request = self.new_update_request('subnets', data, subnet['id']) response = request.get_response(self.api) self.assertEqual(webob.exc.HTTPBadRequest.code, response.status_int) def test_associate_existing_subnet_fail_if_multiple_subnets(self): with self.network() as network: net = network['network'] segment1 = self._test_create_segment(network_id=net['id'], physical_network='phys_net1', segmentation_id=201)['segment'] with self.subnet(network=network, segment_id=None, ip_version=constants.IP_VERSION_4, cidr='10.0.0.0/24') as subnet1, \ self.subnet(network=network, segment_id=None, ip_version=constants.IP_VERSION_4, cidr='10.0.1.0/24') as subnet2: subnet1 = subnet1['subnet'] subnet2 = subnet2['subnet'] data = {'subnet': {'segment_id': segment1['id']}} request = self.new_update_request('subnets', data, subnet1['id']) response = request.get_response(self.api) self.assertEqual(webob.exc.HTTPBadRequest.code, response.status_int) def test_change_existing_subnet_segment_association_not_allowed(self): with self.network() as network: net = network['network'] segment1 = self._test_create_segment(network_id=net['id'], physical_network='phys_net2', segmentation_id=201)['segment'] with self.subnet(network=network, segment_id=segment1['id']) as subnet: subnet = subnet['subnet'] segment2 = self._test_create_segment(network_id=net['id'], physical_network='phys_net2', segmentation_id=202)['segment'] data = {'subnet': {'segment_id': segment2['id']}} request = self.new_update_request('subnets', data, subnet['id']) response = request.get_response(self.api) self.assertEqual(webob.exc.HTTPBadRequest.code, response.status_int) class HostSegmentMappingTestCase(SegmentTestCase): _mechanism_drivers = ['logger'] def setUp(self, plugin=None): cfg.CONF.set_override('mechanism_drivers', self._mechanism_drivers, group='ml2') # NOTE(dasm): ml2_type_vlan requires to be registered before used. # This piece was refactored and removed from .config, so it causes # a problem, when tests are executed with pdb. # There is no problem when tests are running without debugger. driver_type.register_ml2_drivers_vlan_opts() cfg.CONF.set_override('network_vlan_ranges', ['phys_net1', 'phys_net2'], group='ml2_type_vlan') if not plugin: plugin = 'ml2' super(HostSegmentMappingTestCase, self).setUp(plugin=plugin) db.subscribe() def _get_segments_for_host(self, host): ctx = context.get_admin_context() segment_host_mapping = network.SegmentHostMapping.get_objects( ctx, host=host) return {seg_host['segment_id']: seg_host for seg_host in segment_host_mapping} def _register_agent(self, host, mappings=None, plugin=None, start_flag=True): helpers.register_ovs_agent(host=host, bridge_mappings=mappings, plugin=self.plugin, start_flag=start_flag) def _test_one_segment_one_host(self, host): physical_network = 'phys_net1' with self.network() as network: network = network['network'] segment = self._test_create_segment( network_id=network['id'], physical_network=physical_network, segmentation_id=200, network_type=constants.TYPE_VLAN)['segment'] self._register_agent(host, mappings={physical_network: 'br-eth-1'}, plugin=self.plugin) segments_host_db = self._get_segments_for_host(host) self.assertEqual(1, len(segments_host_db)) self.assertEqual(segment['id'], segments_host_db[segment['id']]['segment_id']) self.assertEqual(host, segments_host_db[segment['id']]['host']) return segment class TestMl2HostSegmentMappingNoAgent(HostSegmentMappingTestCase): def setUp(self, plugin=None): if not plugin: plugin = TEST_PLUGIN_KLASS super(TestMl2HostSegmentMappingNoAgent, self).setUp(plugin=plugin) def test_update_segment_host_mapping(self): ctx = context.get_admin_context() host = 'host1' physnets = ['phys_net1'] with self.network() as network: network = network['network'] segment = self._test_create_segment( network_id=network['id'], physical_network='phys_net1', segmentation_id=200, network_type=constants.TYPE_VLAN)['segment'] self._test_create_segment( network_id=network['id'], physical_network='phys_net2', segmentation_id=201, network_type=constants.TYPE_VLAN)['segment'] segments = db.get_segments_with_phys_nets(ctx, physnets) segment_ids = {segment['id'] for segment in segments} db.update_segment_host_mapping(ctx, host, segment_ids) segments_host_db = self._get_segments_for_host(host) self.assertEqual(1, len(segments_host_db)) self.assertEqual(segment['id'], segments_host_db[segment['id']]['segment_id']) self.assertEqual(host, segments_host_db[segment['id']]['host']) def test_map_segment_to_hosts(self): ctx = context.get_admin_context() hosts = {'host1', 'host2', 'host3'} with self.network() as network: network = network['network'] segment = self._test_create_segment( network_id=network['id'], physical_network='phys_net1', segmentation_id=200, network_type=constants.TYPE_VLAN)['segment'] db.map_segment_to_hosts(ctx, segment['id'], hosts) updated_segment = self.plugin.get_segment(ctx, segment['id']) self.assertEqual(hosts, set(updated_segment['hosts'])) def test_get_all_hosts_mapped_with_segments(self): ctx = context.get_admin_context() hosts = set() with self.network() as network: network_id = network['network']['id'] for i in range(1, 3): host = "host%s" % i segment = self._test_create_segment( network_id=network_id, physical_network='phys_net%s' % i, segmentation_id=200 + i, network_type=constants.TYPE_VLAN) db.update_segment_host_mapping( ctx, host, {segment['segment']['id']}) hosts.add(host) # Now they are 2 hosts with segment being mapped. actual_hosts = db.get_hosts_mapped_with_segments(ctx) self.assertEqual(hosts, actual_hosts) class TestMl2HostSegmentMappingOVS(HostSegmentMappingTestCase): _mechanism_drivers = ['openvswitch', 'logger'] mock_path = 'neutron.services.segments.db.update_segment_host_mapping' def test_new_agent(self): host = 'host1' self._test_one_segment_one_host(host) def test_updated_agent_changed_physical_networks(self): host = 'host1' physical_networks = ['phys_net1', 'phys_net2'] networks = [] segments = [] for i in range(len(physical_networks)): with self.network() as network: networks.append(network['network']) segments.append(self._test_create_segment( network_id=networks[i]['id'], physical_network=physical_networks[i], segmentation_id=200, network_type=constants.TYPE_VLAN)['segment']) self._register_agent(host, mappings={physical_networks[0]: 'br-eth-1', physical_networks[1]: 'br-eth-2'}, plugin=self.plugin) segments_host_db = self._get_segments_for_host(host) self.assertEqual(len(physical_networks), len(segments_host_db)) for segment in segments: self.assertEqual(segment['id'], segments_host_db[segment['id']]['segment_id']) self.assertEqual(host, segments_host_db[segment['id']]['host']) self._register_agent(host, mappings={physical_networks[0]: 'br-eth-1'}, plugin=self.plugin) segments_host_db = self._get_segments_for_host(host) self.assertEqual(1, len(segments_host_db)) self.assertEqual(segments[0]['id'], segments_host_db[segments[0]['id']]['segment_id']) self.assertEqual(host, segments_host_db[segments[0]['id']]['host']) def test_same_segment_two_hosts(self): host1 = 'host1' host2 = 'host2' physical_network = 'phys_net1' segment = self._test_one_segment_one_host(host1) self._register_agent(host2, mappings={physical_network: 'br-eth-1'}, plugin=self.plugin) segments_host_db = self._get_segments_for_host(host2) self.assertEqual(1, len(segments_host_db)) self.assertEqual(segment['id'], segments_host_db[segment['id']]['segment_id']) self.assertEqual(host2, segments_host_db[segment['id']]['host']) def test_update_agent_only_change_agent_host_mapping(self): host1 = 'host1' host2 = 'host2' physical_network = 'phys_net1' with self.network() as network: network = network['network'] segment1 = self._test_create_segment( network_id=network['id'], physical_network=physical_network, segmentation_id=200, network_type=constants.TYPE_VLAN)['segment'] self._register_agent(host1, mappings={physical_network: 'br-eth-1'}, plugin=self.plugin) self._register_agent(host2, mappings={physical_network: 'br-eth-1'}, plugin=self.plugin) # Update agent at host2 should only change mapping with host2. other_phys_net = 'phys_net2' segment2 = self._test_create_segment( network_id=network['id'], physical_network=other_phys_net, segmentation_id=201, network_type=constants.TYPE_VLAN)['segment'] self._register_agent(host2, mappings={other_phys_net: 'br-eth-2'}, plugin=self.plugin) # We should have segment1 map to host1 and segment2 map to host2 now segments_host_db1 = self._get_segments_for_host(host1) self.assertEqual(1, len(segments_host_db1)) self.assertEqual(segment1['id'], segments_host_db1[segment1['id']]['segment_id']) self.assertEqual(host1, segments_host_db1[segment1['id']]['host']) segments_host_db2 = self._get_segments_for_host(host2) self.assertEqual(1, len(segments_host_db2)) self.assertEqual(segment2['id'], segments_host_db2[segment2['id']]['segment_id']) self.assertEqual(host2, segments_host_db2[segment2['id']]['host']) def test_new_segment_after_host_reg(self): host1 = 'host1' physical_network = 'phys_net1' segment = self._test_one_segment_one_host(host1) with self.network() as network: network = network['network'] segment2 = self._test_create_segment( network_id=network['id'], physical_network=physical_network, segmentation_id=201, network_type=constants.TYPE_VLAN)['segment'] segments_host_db = self._get_segments_for_host(host1) self.assertEqual(set((segment['id'], segment2['id'])), set(segments_host_db)) def test_segment_deletion_removes_host_mapping(self): host = 'host1' segment = self._test_one_segment_one_host(host) self._delete('segments', segment['id']) segments_host_db = self._get_segments_for_host(host) self.assertFalse(segments_host_db) @mock.patch(mock_path) def test_agent_with_no_mappings(self, mock): host = 'host1' physical_network = 'phys_net1' with self.network() as network: network = network['network'] self._test_create_segment( network_id=network['id'], physical_network=physical_network, segmentation_id=200, network_type=constants.TYPE_VLAN) self._register_agent(host, plugin=self.plugin) segments_host_db = self._get_segments_for_host(host) self.assertFalse(segments_host_db) self.assertFalse(mock.mock_calls) class TestMl2HostSegmentMappingLinuxBridge(TestMl2HostSegmentMappingOVS): _mechanism_drivers = ['linuxbridge', 'logger'] def _register_agent(self, host, mappings=None, plugin=None): helpers.register_linuxbridge_agent(host=host, bridge_mappings=mappings, plugin=self.plugin) class TestMl2HostSegmentMappingMacvtap(TestMl2HostSegmentMappingOVS): _mechanism_drivers = ['macvtap', 'logger'] def _register_agent(self, host, mappings=None, plugin=None): helpers.register_macvtap_agent(host=host, interface_mappings=mappings, plugin=self.plugin) class TestMl2HostSegmentMappingSriovNicSwitch(TestMl2HostSegmentMappingOVS): _mechanism_drivers = ['sriovnicswitch', 'logger'] def _register_agent(self, host, mappings=None, plugin=None): helpers.register_sriovnicswitch_agent(host=host, device_mappings=mappings, plugin=self.plugin) class NoSupportHostSegmentMappingPlugin(db_base_plugin_v2.NeutronDbPluginV2, db.SegmentDbMixin, agents_db.AgentDbMixin): __native_pagination_support = True __native_sorting_support = True supported_extension_aliases = [] class TestHostSegmentMappingNoSupportFromPlugin(HostSegmentMappingTestCase): mock_path = 'neutron.services.segments.db.update_segment_host_mapping' def setUp(self): plugin = ('neutron.tests.unit.extensions.test_segment.' 'NoSupportHostSegmentMappingPlugin') super(TestHostSegmentMappingNoSupportFromPlugin, self).setUp( plugin=plugin) @mock.patch(mock_path) def test_host_segments_not_updated(self, mock): host = 'host1' physical_network = 'phys_net1' with self.network() as network: network = network['network'] self._test_create_segment(network_id=network['id'], physical_network=physical_network, segmentation_id=200, network_type=constants.TYPE_VLAN) self._register_agent(host, mappings={physical_network: 'br-eth-1'}, plugin=self.plugin) segments_host_db = self._get_segments_for_host(host) self.assertFalse(segments_host_db) self.assertFalse(mock.mock_calls) class TestMl2HostSegmentMappingAgentServerSynch(HostSegmentMappingTestCase): _mechanism_drivers = ['openvswitch', 'logger'] mock_path = 'neutron.services.segments.db.update_segment_host_mapping' @mock.patch(mock_path) def test_starting_server_processes_agents(self, mock_function): host = 'agent_updating_starting_server' physical_network = 'phys_net1' self._register_agent(host, mappings={physical_network: 'br-eth-1'}, plugin=self.plugin, start_flag=False) self.assertIn(host, db.reported_hosts) self.assertEqual(1, mock_function.call_count) expected_call = mock.call(mock.ANY, host, set()) mock_function.assert_has_calls([expected_call]) @mock.patch(mock_path) def test_starting_agent_is_processed(self, mock_function): host = 'starting_agent' physical_network = 'phys_net1' self._register_agent(host, mappings={physical_network: 'br-eth-1'}, plugin=self.plugin, start_flag=False) self.assertIn(host, db.reported_hosts) self._register_agent(host, mappings={physical_network: 'br-eth-1'}, plugin=self.plugin, start_flag=True) self.assertIn(host, db.reported_hosts) self.assertEqual(1, mock_function.call_count) expected_call = mock.call(mock.ANY, host, set()) mock_function.assert_has_calls([expected_call]) @mock.patch(mock_path) def test_no_starting_agent_is_not_processed(self, mock_function): host = 'agent_with_no_start_update' physical_network = 'phys_net1' self._register_agent(host, mappings={physical_network: 'br-eth-1'}, plugin=self.plugin, start_flag=False) self.assertIn(host, db.reported_hosts) mock_function.reset_mock() self._register_agent(host, mappings={physical_network: 'br-eth-1'}, plugin=self.plugin, start_flag=False) self.assertIn(host, db.reported_hosts) mock_function.assert_not_called() class SegmentAwareIpamTestCase(SegmentTestCase): def _setup_host_mappings(self, mappings=()): ctx = context.get_admin_context() for segment_id, host in mappings: network.SegmentHostMapping( ctx, segment_id=segment_id, host=host).create() def _create_test_segment_with_subnet(self, network=None, cidr='2001:db8:0:0::/64', physnet='physnet'): """Creates one network with one segment and one subnet""" network, segment = self._create_test_network_and_segment(network, physnet) subnet = self._create_test_subnet_with_segment(network, segment, cidr) return network, segment, subnet def _create_test_network_and_segment(self, network=None, physnet='physnet'): if not network: with self.network() as network: pass segment = self._test_create_segment( network_id=network['network']['id'], physical_network=physnet, network_type=constants.TYPE_VLAN) return network, segment def _create_test_subnet_with_segment(self, network, segment, cidr='2001:db8:0:0::/64', allocation_pools=None): ip_version = netaddr.IPNetwork(cidr).version if cidr else None with self.subnet(network=network, segment_id=segment['segment']['id'], ip_version=ip_version, cidr=cidr, allocation_pools=allocation_pools) as subnet: self._validate_l2_adjacency(network['network']['id'], is_adjacent=False) return subnet def _create_test_slaac_subnet_with_segment( self, network, segment, cidr='2001:db8:0:0::/64'): with self.subnet(network=network, segment_id=segment['segment']['id'], ip_version=constants.IP_VERSION_6, ipv6_ra_mode=constants.IPV6_SLAAC, ipv6_address_mode=constants.IPV6_SLAAC, cidr=cidr, allocation_pools=None) as subnet: self._validate_l2_adjacency(network['network']['id'], is_adjacent=False) return subnet def _validate_l2_adjacency(self, network_id, is_adjacent): request = self.new_show_request('networks', network_id) response = self.deserialize(self.fmt, request.get_response(self.api)) self.assertEqual(is_adjacent, response['network'][l2adj_apidef.L2_ADJACENCY]) class TestSegmentAwareIpam(SegmentAwareIpamTestCase): def _create_test_segments_with_subnets(self, num): """Creates one network with num segments and num subnets""" with self.network() as network: segments, subnets = [], [] for i in range(num): cidr = '2001:db8:0:%s::/64' % i physnet = 'physnet%s' % i _net, segment, subnet = self._create_test_segment_with_subnet( network=network, cidr=cidr, physnet=physnet) segments.append(segment) subnets.append(subnet) return network, segments, subnets def _create_net_two_segments_four_slaac_subnets(self): with self.network() as network: segment_a = self._test_create_segment( network_id=network['network']['id'], physical_network='physnet_a', network_type=constants.TYPE_FLAT) segment_b = self._test_create_segment( network_id=network['network']['id'], physical_network='physnet_b', network_type=constants.TYPE_FLAT) subnet_a0 = self._create_test_slaac_subnet_with_segment( network, segment_a, '2001:db8:a:0::/64') subnet_a1 = self._create_test_slaac_subnet_with_segment( network, segment_a, '2001:db8:a:1::/64') subnet_b0 = self._create_test_slaac_subnet_with_segment( network, segment_b, '2001:db8:b:0::/64') subnet_b1 = self._create_test_slaac_subnet_with_segment( network, segment_b, '2001:db8:b:1::/64') return (network, segment_a, segment_b, subnet_a0, subnet_a1, subnet_b0, subnet_b1) def test_port_create_with_segment_subnets(self): """No binding information is provided, defer IP allocation""" network, segment, subnet = self._create_test_segment_with_subnet() response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id']) res = self.deserialize(self.fmt, response) # Don't allocate IPs in this case because we didn't give binding info self.assertEqual(0, len(res['port']['fixed_ips'])) def test_port_create_fixed_ips_with_segment_subnets_no_binding_info(self): """Fixed IP provided and no binding info, do not defer IP allocation""" network, segment, subnet = self._create_test_segment_with_subnet() response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], fixed_ips=[ {'subnet_id': subnet['subnet']['id']} ]) res = self.deserialize(self.fmt, response) # We gave fixed_ips, allocate IPs in this case despite no binding info self._validate_immediate_ip_allocation(res['port']['id']) def _assert_one_ip_in_subnet(self, response, cidr): res = self.deserialize(self.fmt, response) self.assertEqual(1, len(res['port']['fixed_ips'])) ip = res['port']['fixed_ips'][0]['ip_address'] ip_net = netaddr.IPNetwork(cidr) self.assertIn(ip, ip_net) def test_port_create_with_binding_information(self): """Binding information is provided, subnets are on segments""" network, segments, subnets = self._create_test_segments_with_subnets(3) # Map the host to the middle segment (by mocking host/segment mapping) self._setup_host_mappings([ (segments[1]['segment']['id'], 'fakehost'), (segments[1]['segment']['id'], 'otherhost'), (segments[0]['segment']['id'], 'thirdhost')]) response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: 'fakehost'}) res = self.deserialize(self.fmt, response) self._validate_immediate_ip_allocation(res['port']['id']) # Since host mapped to middle segment, IP must come from middle subnet self._assert_one_ip_in_subnet(response, subnets[1]['subnet']['cidr']) def test_port_create_with_binding_and_no_subnets(self): """Binding information is provided, no subnets.""" with self.network() as network: segment = self._test_create_segment( network_id=network['network']['id'], physical_network='physnet', network_type=constants.TYPE_VLAN) # Map the host to the segment self._setup_host_mappings([(segment['segment']['id'], 'fakehost')]) response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: 'fakehost'}) res = self.deserialize(self.fmt, response) # No subnets, so no allocation. But, it shouldn't be an error. self.assertEqual(0, len(res['port']['fixed_ips'])) def test_port_create_with_binding_information_fallback(self): """Binding information is provided, subnets not on segments""" with self.network() as network: with self.subnet(network=network, ip_version=constants.IP_VERSION_6, cidr='2001:db8:0:0::/64') as subnet: segment = self._test_create_segment( network_id=network['network']['id'], physical_network='physnet', network_type=constants.TYPE_VLAN) self._validate_l2_adjacency(network['network']['id'], is_adjacent=True) # Map the host to the segment self._setup_host_mappings([(segment['segment']['id'], 'fakehost')]) response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: 'fakehost'}) res = self.deserialize(self.fmt, response) self._validate_immediate_ip_allocation(res['port']['id']) # Since the subnet is not on a segment, fall back to it self._assert_one_ip_in_subnet(response, subnet['subnet']['cidr']) def test_port_create_on_unconnected_host(self): """Binding information provided, host not connected to any segment""" network, segment, _subnet = self._create_test_segment_with_subnet() response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: 'fakehost'}) res = self.deserialize(self.fmt, response) self.assertEqual(webob.exc.HTTPConflict.code, response.status_int) self.assertEqual(segment_exc.HostNotConnectedToAnySegment.__name__, res['NeutronError']['type']) # Ensure that mapping the segment to other hosts doesn't trip it up self._setup_host_mappings([(segment['segment']['id'], 'otherhost')]) response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: 'fakehost'}) res = self.deserialize(self.fmt, response) self.assertEqual(webob.exc.HTTPConflict.code, response.status_int) self.assertEqual(segment_exc.HostNotConnectedToAnySegment.__name__, res['NeutronError']['type']) def test_port_create_on_multiconnected_host(self): """Binding information provided, host connected to multiple segments""" network, segments, subnets = self._create_test_segments_with_subnets(2) # This host is bound to multiple hosts self._setup_host_mappings([(segments[0]['segment']['id'], 'fakehost'), (segments[1]['segment']['id'], 'fakehost')]) response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: 'fakehost'}) res = self.deserialize(self.fmt, response) self.assertEqual(webob.exc.HTTPConflict.code, response.status_int) self.assertEqual(segment_exc.HostConnectedToMultipleSegments.__name__, res['NeutronError']['type']) def test_port_update_with_fixed_ips_ok_if_no_binding_host(self): """No binding host information is provided, subnets on segments""" with self.network() as network: segment = self._test_create_segment( network_id=network['network']['id'], physical_network='physnet', network_type=constants.TYPE_VLAN) # Create a port with no IP address (since there is no subnet) port = self._create_deferred_ip_port(network) # Create the subnet and try to update the port to get an IP with self.subnet(network=network, segment_id=segment['segment']['id']) as subnet: # Try requesting an IP (but the only subnet is on a segment) data = {'port': { 'fixed_ips': [{'subnet_id': subnet['subnet']['id']}]}} port_id = port['port']['id'] port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) # The IP is allocated since there is no binding host info any # subnet can be used for allocation. self.assertEqual(webob.exc.HTTPOk.code, response.status_int) def test_port_update_with_fixed_ips_fail_if_host_not_on_segment(self): """Binding information is provided, subnets on segments. Update to subnet on different segment fails. """ network, segments, subnets = self._create_test_segments_with_subnets(2) # Setup host mappings self._setup_host_mappings([(segments[0]['segment']['id'], 'fakehost')]) # Create a port and validate immediate ip allocation res = self._create_port_and_show(network, arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: 'fakehost'}) self._validate_immediate_ip_allocation(res['port']['id']) # Try requesting an new IP, but the subnet does not match host segment port_id = res['port']['id'] data = {'port': { 'fixed_ips': [{'subnet_id': subnets[1]['subnet']['id']}]}} port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) # Port update fails. self.assertEqual(webob.exc.HTTPBadRequest.code, response.status_int) def _create_port_and_show(self, network, **kwargs): response = self._create_port( self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], **kwargs) port = self.deserialize(self.fmt, response) request = self.new_show_request('ports', port['port']['id']) return self.deserialize(self.fmt, request.get_response(self.api)) def test_port_create_with_no_fixed_ips_no_ipam_on_routed_network(self): """Ports requesting no fixed_ips not deferred, even on routed net""" with self.network() as network: segment = self._test_create_segment( network_id=network['network']['id'], physical_network='physnet', network_type=constants.TYPE_VLAN) with self.subnet(network=network, segment_id=segment['segment']['id']): pass # Create an unbound port requesting no IP addresses response = self._create_port_and_show(network, fixed_ips=[]) self.assertEqual([], response['port']['fixed_ips']) self.assertEqual(ipalloc_apidef.IP_ALLOCATION_NONE, response['port'][ipalloc_apidef.IP_ALLOCATION]) def test_port_create_with_no_fixed_ips_no_ipam(self): """Ports without addresses on non-routed networks are not deferred""" with self.network() as network: with self.subnet(network=network): pass # Create an unbound port requesting no IP addresses response = self._create_port_and_show(network, fixed_ips=[]) self.assertEqual([], response['port']['fixed_ips']) self.assertEqual(ipalloc_apidef.IP_ALLOCATION_NONE, response['port'][ipalloc_apidef.IP_ALLOCATION]) def test_port_without_ip_not_deferred(self): """Ports without addresses on non-routed networks are not deferred""" with self.network() as network: pass # Create a bound port with no IP address (since there is no subnet) response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: 'fakehost'}) port = self.deserialize(self.fmt, response) request = self.new_show_request('ports', port['port']['id']) response = self.deserialize(self.fmt, request.get_response(self.api)) self.assertEqual([], response['port']['fixed_ips']) self.assertEqual(ipalloc_apidef.IP_ALLOCATION_IMMEDIATE, response['port'][ipalloc_apidef.IP_ALLOCATION]) def test_port_without_ip_not_deferred_no_binding(self): """Ports without addresses on non-routed networks are not deferred""" with self.network() as network: pass # Create a unbound port with no IP address (since there is no subnet) response = self._create_port_and_show(network) self.assertEqual([], response['port']['fixed_ips']) self.assertEqual(ipalloc_apidef.IP_ALLOCATION_IMMEDIATE, response['port'][ipalloc_apidef.IP_ALLOCATION]) def test_port_update_is_host_aware(self): """Binding information is provided, subnets on segments""" with self.network() as network: segment = self._test_create_segment( network_id=network['network']['id'], physical_network='physnet', network_type=constants.TYPE_VLAN) # Map the host to the segment self._setup_host_mappings([(segment['segment']['id'], 'fakehost')]) # Create a bound port with no IP address (since there is no subnet) response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: 'fakehost'}) port = self.deserialize(self.fmt, response) # Create the subnet and try to update the port to get an IP with self.subnet(network=network, segment_id=segment['segment']['id']) as subnet: self._validate_l2_adjacency(network['network']['id'], is_adjacent=False) # Try requesting an IP (but the only subnet is on a segment) data = {'port': { 'fixed_ips': [{'subnet_id': subnet['subnet']['id']}]}} port_id = port['port']['id'] port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) # Since port is bound and there is a mapping to segment, it succeeds. self.assertEqual(webob.exc.HTTPOk.code, response.status_int) self._assert_one_ip_in_subnet(response, subnet['subnet']['cidr']) def _validate_deferred_ip_allocation(self, port_id): request = self.new_show_request('ports', port_id) response = self.deserialize(self.fmt, request.get_response(self.api)) self.assertEqual(ipalloc_apidef.IP_ALLOCATION_DEFERRED, response['port'][ipalloc_apidef.IP_ALLOCATION]) ips = response['port']['fixed_ips'] self.assertEqual(0, len(ips)) def _validate_immediate_ip_allocation(self, port_id): request = self.new_show_request('ports', port_id) response = self.deserialize(self.fmt, request.get_response(self.api)) self.assertEqual(ipalloc_apidef.IP_ALLOCATION_IMMEDIATE, response['port'][ipalloc_apidef.IP_ALLOCATION]) ips = response['port']['fixed_ips'] self.assertNotEqual(0, len(ips)) def _create_deferred_ip_port(self, network): response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id']) port = self.deserialize(self.fmt, response) ips = port['port']['fixed_ips'] self.assertEqual(0, len(ips)) return port def test_port_update_deferred_allocation(self): """Binding information is provided on update, subnets on segments""" network, segment, subnet = self._create_test_segment_with_subnet() # Map the host to the segment self._setup_host_mappings([(segment['segment']['id'], 'fakehost')]) port = self._create_deferred_ip_port(network) self._validate_deferred_ip_allocation(port['port']['id']) # Try requesting an IP (but the only subnet is on a segment) data = {'port': {portbindings.HOST_ID: 'fakehost'}} port_id = port['port']['id'] port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) # Port update succeeds and allocates a new IP address. self.assertEqual(webob.exc.HTTPOk.code, response.status_int) self._assert_one_ip_in_subnet(response, subnet['subnet']['cidr']) def test_port_update_deferred_allocation_no_segments(self): """Binding information is provided, subnet created after port""" with self.network() as network: pass port = self._create_deferred_ip_port(network) # Create the subnet and try to update the port to get an IP with self.subnet(network=network): data = {'port': {portbindings.HOST_ID: 'fakehost'}} port_id = port['port']['id'] port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) self.assertEqual(webob.exc.HTTPOk.code, response.status_int) res = self.deserialize(self.fmt, response) self.assertEqual(0, len(res['port']['fixed_ips'])) def test_port_update_deferred_allocation_no_ipam(self): """Binding information is provided on update. Don't allocate.""" with self.network() as network: with self.subnet(network=network): pass response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], fixed_ips=[]) port = self.deserialize(self.fmt, response) ips = port['port']['fixed_ips'] self.assertEqual(0, len(ips)) # Create the subnet and try to update the port to get an IP data = {'port': {portbindings.HOST_ID: 'fakehost'}} port_id = port['port']['id'] port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) self.assertEqual(webob.exc.HTTPOk.code, response.status_int) res = self.deserialize(self.fmt, response) self.assertEqual(0, len(res['port']['fixed_ips'])) def test_port_update_deferred_allocation_no_segments_manual_alloc(self): """Binding information is provided, subnet created after port""" with self.network() as network: pass port = self._create_deferred_ip_port(network) # Create the subnet and try to update the port to get an IP with self.subnet(network=network) as subnet: data = {'port': { portbindings.HOST_ID: 'fakehost', 'fixed_ips': [{'subnet_id': subnet['subnet']['id']}]}} port_id = port['port']['id'] port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) self.assertEqual(webob.exc.HTTPOk.code, response.status_int) self._assert_one_ip_in_subnet(response, subnet['subnet']['cidr']) # Do a show to be sure that only one IP is recorded port_req = self.new_show_request('ports', port_id) response = port_req.get_response(self.api) self.assertEqual(webob.exc.HTTPOk.code, response.status_int) self._assert_one_ip_in_subnet(response, subnet['subnet']['cidr']) def test_port_update_deferred_allocation_no_segments_empty_alloc(self): """Binding information is provided, subnet created after port""" with self.network() as network: pass port = self._create_deferred_ip_port(network) # Create the subnet and update the port but specify no IPs with self.subnet(network=network): data = {'port': { portbindings.HOST_ID: 'fakehost', 'fixed_ips': []}} port_id = port['port']['id'] port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) self.assertEqual(webob.exc.HTTPOk.code, response.status_int) res = self.deserialize(self.fmt, response) # Since I specifically requested no IP addresses, I shouldn't get one. self.assertEqual(0, len(res['port']['fixed_ips'])) def test_port_update_deferred_allocation_no_host_mapping(self): """Binding information is provided on update, subnets on segments""" network, segment, subnet = self._create_test_segment_with_subnet() port = self._create_deferred_ip_port(network) self._validate_deferred_ip_allocation(port['port']['id']) # Try requesting an IP (but the only subnet is on a segment) data = {'port': {portbindings.HOST_ID: 'fakehost'}} port_id = port['port']['id'] port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) res = self.deserialize(self.fmt, response) # Gets conflict because it can't map the host to a segment self.assertEqual(webob.exc.HTTPConflict.code, response.status_int) self.assertEqual(segment_exc.HostNotConnectedToAnySegment.__name__, res['NeutronError']['type']) def test_port_update_deferred_allocation_multiple_host_mapping(self): """Binding information is provided on update, subnets on segments""" network, segments, _s = self._create_test_segments_with_subnets(2) port = self._create_deferred_ip_port(network) self._validate_deferred_ip_allocation(port['port']['id']) # This host is bound to multiple segments self._setup_host_mappings([(segments[0]['segment']['id'], 'fakehost'), (segments[1]['segment']['id'], 'fakehost')]) # Try requesting an IP (but the only subnet is on a segment) data = {'port': {portbindings.HOST_ID: 'fakehost'}} port_id = port['port']['id'] port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) res = self.deserialize(self.fmt, response) # Gets conflict because it can't map the host to a segment self.assertEqual(webob.exc.HTTPConflict.code, response.status_int) self.assertEqual(segment_exc.HostConnectedToMultipleSegments.__name__, res['NeutronError']['type']) def test_port_update_allocate_no_segments(self): """Binding information is provided, subnet created after port""" with self.network() as network: pass # Create a bound port with no IP address (since there is not subnet) port = self._create_deferred_ip_port(network) # Create the subnet and try to update the port to get an IP with self.subnet(network=network) as subnet: # Try requesting an IP (but the only subnet is on a segment) data = {'port': { 'fixed_ips': [{'subnet_id': subnet['subnet']['id']}]}} port_id = port['port']['id'] port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) # Since port is bound and there is a mapping to segment, it succeeds. self.assertEqual(webob.exc.HTTPOk.code, response.status_int) self._assert_one_ip_in_subnet(response, subnet['subnet']['cidr']) def test_port_update_deferred_allocation_no_ips(self): """Binding information is provided on update, subnets on segments""" network, segments, subnets = self._create_test_segments_with_subnets(2) self._setup_host_mappings([(segments[0]['segment']['id'], 'fakehost2'), (segments[1]['segment']['id'], 'fakehost')]) port = self._create_deferred_ip_port(network) # Update the subnet on the second segment to be out of IPs subnet_data = {'subnet': {'allocation_pools': []}} subnet_req = self.new_update_request('subnets', subnet_data, subnets[1]['subnet']['id']) subnet_response = subnet_req.get_response(self.api) res = self.deserialize(self.fmt, subnet_response) # Try requesting an IP (but the subnet ran out of ips) data = {'port': {portbindings.HOST_ID: 'fakehost'}} port_id = port['port']['id'] port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) res = self.deserialize(self.fmt, response) # Since port is bound and there is a mapping to segment, it succeeds. self.assertEqual(webob.exc.HTTPConflict.code, response.status_int) self.assertEqual(n_exc.IpAddressGenerationFailure.__name__, res['NeutronError']['type']) def test_port_update_fails_if_host_on_wrong_segment(self): """Update a port with existing IPs to a host where they don't work""" network, segments, subnets = self._create_test_segments_with_subnets(2) self._setup_host_mappings([(segments[0]['segment']['id'], 'fakehost2'), (segments[1]['segment']['id'], 'fakehost')]) # Create a bound port with an IP address response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: 'fakehost'}) self._assert_one_ip_in_subnet(response, subnets[1]['subnet']['cidr']) port = self.deserialize(self.fmt, response) # Now, try to update binding to a host on the other segment data = {'port': {portbindings.HOST_ID: 'fakehost2'}} port_req = self.new_update_request('ports', data, port['port']['id']) response = port_req.get_response(self.api) # It fails since the IP address isn't compatible with the new segment self.assertEqual(webob.exc.HTTPConflict.code, response.status_int) def test_port_update_fails_if_host_on_good_segment(self): """Update a port with existing IPs to a host where they don't work""" network, segments, subnets = self._create_test_segments_with_subnets(2) self._setup_host_mappings([(segments[0]['segment']['id'], 'fakehost2'), (segments[1]['segment']['id'], 'fakehost1'), (segments[1]['segment']['id'], 'fakehost')]) # Create a bound port with an IP address response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: 'fakehost'}) self._assert_one_ip_in_subnet(response, subnets[1]['subnet']['cidr']) port = self.deserialize(self.fmt, response) # Now, try to update binding to another host in same segment data = {'port': {portbindings.HOST_ID: 'fakehost1'}} port_req = self.new_update_request('ports', data, port['port']['id']) response = port_req.get_response(self.api) # Since the new host is in the same segment, it succeeds. self.assertEqual(webob.exc.HTTPOk.code, response.status_int) def test_port_update_deferred_allocation_binding_info_and_new_mac(self): """Binding information and new mac address is provided on update""" network, segment, subnet = self._create_test_segment_with_subnet() # Map the host to the segment self._setup_host_mappings([(segment['segment']['id'], 'fakehost')]) port = self._create_deferred_ip_port(network) self._validate_deferred_ip_allocation(port['port']['id']) # Try requesting an IP (but the only subnet is on a segment) data = {'port': {portbindings.HOST_ID: 'fakehost', port_apidef.PORT_MAC_ADDRESS: '00:00:00:00:00:01'}} port_id = port['port']['id'] port_req = self.new_update_request('ports', data, port_id) response = port_req.get_response(self.api) # Port update succeeds and allocates a new IP address. self.assertEqual(webob.exc.HTTPOk.code, response.status_int) self._assert_one_ip_in_subnet(response, subnet['subnet']['cidr']) def test_slaac_segment_aware_no_binding_info(self): (network, segment_a, segment_b, subnet_a0, subnet_a1, subnet_b0, subnet_b1) = self._create_net_two_segments_four_slaac_subnets() # Create a port with no IP address (since there is no subnet) port_deferred = self._create_deferred_ip_port(network) self._validate_deferred_ip_allocation(port_deferred['port']['id']) def test_slaac_segment_aware_immediate_fixed_ips_no_binding_info_(self): (network, segment_a, segment_b, subnet_a0, subnet_a1, subnet_b0, subnet_b1) = self._create_net_two_segments_four_slaac_subnets() # Create two ports, port_a with subnet_a0 in fixed_ips and port_b # with subnet_b0 in fixed_ips port_a = self._create_port_and_show( network, fixed_ips=[{'subnet_id': subnet_a0['subnet']['id']}]) port_b = self._create_port_and_show( network, fixed_ips=[{'subnet_id': subnet_b0['subnet']['id']}]) self._validate_immediate_ip_allocation(port_a['port']['id']) self._validate_immediate_ip_allocation(port_b['port']['id']) self.assertEqual(2, len(port_a['port']['fixed_ips'])) self.assertEqual(2, len(port_b['port']['fixed_ips'])) port_a_snet_ids = [f['subnet_id'] for f in port_a['port']['fixed_ips']] port_b_snet_ids = [f['subnet_id'] for f in port_b['port']['fixed_ips']] self.assertIn(subnet_a0['subnet']['id'], port_a_snet_ids) self.assertIn(subnet_a1['subnet']['id'], port_a_snet_ids) self.assertIn(subnet_b0['subnet']['id'], port_b_snet_ids) self.assertIn(subnet_b1['subnet']['id'], port_b_snet_ids) self.assertNotIn(subnet_a0['subnet']['id'], port_b_snet_ids) self.assertNotIn(subnet_a1['subnet']['id'], port_b_snet_ids) self.assertNotIn(subnet_b0['subnet']['id'], port_a_snet_ids) self.assertNotIn(subnet_b1['subnet']['id'], port_a_snet_ids) def test_slaac_segment_aware_immediate_with_binding_info(self): (network, segment_a, segment_b, subnet_a0, subnet_a1, subnet_b0, subnet_b1) = self._create_net_two_segments_four_slaac_subnets() self._setup_host_mappings([(segment_a['segment']['id'], 'fakehost_a')]) # Create a port with host ID, validate immediate allocation on subnets # with correct segment_id. response = self._create_port(self.fmt, net_id=network['network']['id'], tenant_id=network['network']['tenant_id'], arg_list=(portbindings.HOST_ID,), **{portbindings.HOST_ID: 'fakehost_a'}) res = self.deserialize(self.fmt, response) self._validate_immediate_ip_allocation(res['port']['id']) # Since host mapped to segment_a, IP's must come from subnets: # subnet_a0 and subnet_a1 self.assertEqual(2, len(res['port']['fixed_ips'])) res_subnet_ids = [f['subnet_id'] for f in res['port']['fixed_ips']] self.assertIn(subnet_a0['subnet']['id'], res_subnet_ids) self.assertIn(subnet_a1['subnet']['id'], res_subnet_ids) self.assertNotIn(subnet_b0['subnet']['id'], res_subnet_ids) self.assertNotIn(subnet_b1['subnet']['id'], res_subnet_ids) def test_slaac_segment_aware_add_subnet(self): (network, segment_a, segment_b, subnet_a0, subnet_a1, subnet_b0, subnet_b1) = self._create_net_two_segments_four_slaac_subnets() # Create a port with no IP address (since there is no subnet) port_deferred = self._create_deferred_ip_port(network) self._validate_deferred_ip_allocation(port_deferred['port']['id']) # Create two ports, port_a with subnet_a0 in fixed_ips and port_b # with subnet_b0 in fixed_ips port_a = self._create_port_and_show( network, fixed_ips=[{'subnet_id': subnet_a0['subnet']['id']}]) port_b = self._create_port_and_show( network, fixed_ips=[{'subnet_id': subnet_b0['subnet']['id']}]) self._validate_immediate_ip_allocation(port_a['port']['id']) self._validate_immediate_ip_allocation(port_b['port']['id']) self.assertEqual(2, len(port_a['port']['fixed_ips'])) self.assertEqual(2, len(port_b['port']['fixed_ips'])) # Add another subnet on segment_a subnet_a2 = self._create_test_slaac_subnet_with_segment( network, segment_a, '2001:db8:a:2::/64') # The port with deferred allocation should not have an allocation req = self.new_show_request('ports', port_deferred['port']['id']) res = req.get_response(self.api) port_deferred = self.deserialize(self.fmt, res) self._validate_deferred_ip_allocation(port_deferred['port']['id']) self.assertEqual(0, len(port_deferred['port']['fixed_ips'])) # port_a should get an allocation on the new subnet. # port_b does not get an allocation. req = self.new_show_request('ports', port_a['port']['id']) res = req.get_response(self.api) port_a = self.deserialize(self.fmt, res) req = self.new_show_request('ports', port_b['port']['id']) res = req.get_response(self.api) port_b = self.deserialize(self.fmt, res) self.assertEqual(3, len(port_a['port']['fixed_ips'])) self.assertEqual(2, len(port_b['port']['fixed_ips'])) port_a_snet_ids = [f['subnet_id'] for f in port_a['port']['fixed_ips']] self.assertIn(subnet_a2['subnet']['id'], port_a_snet_ids) def test_slaac_segment_aware_delete_subnet(self): (network, segment_a, segment_b, subnet_a0, subnet_a1, subnet_b0, subnet_b1) = self._create_net_two_segments_four_slaac_subnets() # Create two ports, port_a with subnet_a0 in fixed_ips and port_b # with subnet_b0 in fixed_ips port_a = self._create_port_and_show( network, fixed_ips=[{'subnet_id': subnet_a0['subnet']['id']}]) port_b = self._create_port_and_show( network, fixed_ips=[{'subnet_id': subnet_b0['subnet']['id']}]) self._validate_immediate_ip_allocation(port_a['port']['id']) self._validate_immediate_ip_allocation(port_b['port']['id']) self.assertEqual(2, len(port_a['port']['fixed_ips'])) self.assertEqual(2, len(port_b['port']['fixed_ips'])) # Delete subnet_b1 on segment_b, port_a should keep it's allocations # on the new subnet. Allocation for deleted subnet removed on port_b. req = self.new_delete_request('subnets', subnet_b1['subnet']['id']) res = req.get_response(self.api) self.assertEqual(webob.exc.HTTPNoContent.code, res.status_int) req = self.new_show_request('ports', port_a['port']['id']) res = req.get_response(self.api) port_a = self.deserialize(self.fmt, res) req = self.new_show_request('ports', port_b['port']['id']) res = req.get_response(self.api) port_b = self.deserialize(self.fmt, res) self.assertEqual(2, len(port_a['port']['fixed_ips'])) self.assertEqual(1, len(port_b['port']['fixed_ips'])) port_b_snet_ids = [f['subnet_id'] for f in port_b['port']['fixed_ips']] self.assertNotIn(subnet_b1['subnet']['id'], port_b_snet_ids) def test_slaac_segment_aware_delete_last_subnet_on_segment_fails(self): (network, segment_a, segment_b, subnet_a0, subnet_a1, subnet_b0, subnet_b1) = self._create_net_two_segments_four_slaac_subnets() # Create two ports, port_a with subnet_a0 in fixed_ips and port_b # with subnet_b0 in fixed_ips port_a = self._create_port_and_show( network, fixed_ips=[{'subnet_id': subnet_a0['subnet']['id']}]) port_b = self._create_port_and_show( network, fixed_ips=[{'subnet_id': subnet_b0['subnet']['id']}]) self._validate_immediate_ip_allocation(port_a['port']['id']) self._validate_immediate_ip_allocation(port_b['port']['id']) self.assertEqual(2, len(port_a['port']['fixed_ips'])) self.assertEqual(2, len(port_b['port']['fixed_ips'])) # Delete subnet_b1 on segment_b req = self.new_delete_request('subnets', subnet_b1['subnet']['id']) res = req.get_response(self.api) self.assertEqual(webob.exc.HTTPNoContent.code, res.status_int) # Delete subnet_b0 on segment_b fails because port_b has no other # allocation, SubnetInUse req = self.new_delete_request('subnets', subnet_b0['subnet']['id']) res = req.get_response(self.api) self.assertEqual(webob.exc.HTTPConflict.code, res.status_int) # Delete port_b req = self.new_delete_request('ports', port_b['port']['id']) res = req.get_response(self.api) self.assertEqual(webob.exc.HTTPNoContent.code, res.status_int) # Try to delete subnet_b0 again, should not fail with no ports req = self.new_delete_request('subnets', subnet_b0['subnet']['id']) res = req.get_response(self.api) self.assertEqual(webob.exc.HTTPNoContent.code, res.status_int) class TestSegmentAwareIpamML2(TestSegmentAwareIpam): VLAN_MIN = 200 VLAN_MAX = 209 def setUp(self): # NOTE(mlavalle): ml2_type_vlan requires to be registered before used. # This piece was refactored and removed from .config, so it causes # a problem, when tests are executed with pdb. # There is no problem when tests are running without debugger. driver_type.register_ml2_drivers_vlan_opts() cfg.CONF.set_override( 'network_vlan_ranges', ['physnet:%s:%s' % (self.VLAN_MIN, self.VLAN_MAX), 'physnet0:%s:%s' % (self.VLAN_MIN, self.VLAN_MAX), 'physnet1:%s:%s' % (self.VLAN_MIN, self.VLAN_MAX), 'physnet2:%s:%s' % (self.VLAN_MIN, self.VLAN_MAX)], group='ml2_type_vlan') super(TestSegmentAwareIpamML2, self).setUp(plugin='ml2') def test_segmentation_id_stored_in_db(self): network, segment, subnet = self._create_test_segment_with_subnet() self.assertTrue(self.VLAN_MIN <= segment['segment']['segmentation_id'] <= self.VLAN_MAX) retrieved_segment = self._show('segments', segment['segment']['id']) self.assertEqual(segment['segment']['segmentation_id'], retrieved_segment['segment']['segmentation_id']) class TestNovaSegmentNotifier(SegmentAwareIpamTestCase): _mechanism_drivers = ['openvswitch', 'logger'] def setUp(self): ml2_config.register_ml2_plugin_opts() driver_type.register_ml2_drivers_vlan_opts() cfg.CONF.set_override('mechanism_drivers', self._mechanism_drivers, group='ml2') cfg.CONF.set_override('network_vlan_ranges', ['physnet:200:209', 'physnet0:200:209', 'physnet1:200:209', 'physnet2:200:209'], group='ml2_type_vlan') super(TestNovaSegmentNotifier, self).setUp(plugin='ml2') # Need notifier here self.patch_notifier.stop() self._mock_keystone_auth() self.segments_plugin = directory.get_plugin(ext_segment.SEGMENTS) nova_updater = self.segments_plugin.nova_updater nova_updater.p_client = mock.MagicMock() self.mock_p_client = nova_updater.p_client nova_updater.n_client = mock.MagicMock() self.mock_n_client = nova_updater.n_client self.batch_notifier = nova_updater.batch_notifier self.batch_notifier._waiting_to_send = True def _mock_keystone_auth(self): # Use to remove MissingAuthPlugin exception when notifier is needed self.mock_load_auth_p = mock.patch( 'keystoneauth1.loading.load_auth_from_conf_options') self.mock_load_auth = self.mock_load_auth_p.start() self.mock_request_p = mock.patch( 'keystoneauth1.session.Session.request') self.mock_request = self.mock_request_p.start() def _calculate_inventory_total_and_reserved(self, subnet): total = 0 reserved = 0 allocation_pools = subnet.get('allocation_pools') or [] for pool in allocation_pools: total += int(netaddr.IPAddress(pool['end']) - netaddr.IPAddress(pool['start'])) + 1 if total: if subnet.get('gateway_ip'): total += 1 reserved += 1 if subnet.get('enable_dhcp'): reserved += 1 return total, reserved def test__create_nova_inventory_no_microversion(self): network, segment = self._create_test_network_and_segment() segment_id = segment['segment']['id'] aggregate = mock.Mock(spec=["id"]) aggregate.id = 1 self.mock_n_client.aggregates.create.return_value = aggregate with mock.patch.object(seg_plugin.LOG, 'exception') as log: self.assertRaises( AttributeError, self.segments_plugin.nova_updater._create_nova_inventory, segment_id, 63, 2, []) self.assertTrue(log.called) def _assert_inventory_creation(self, segment_id, aggregate, subnet): self.batch_notifier._notify() self.mock_p_client.get_inventory.assert_called_with( segment_id, seg_plugin.IPV4_RESOURCE_CLASS) self.mock_p_client.update_resource_provider_inventory.\ assert_not_called() name = seg_plugin.SEGMENT_NAME_STUB % segment_id resource_provider = {'name': name, 'uuid': segment_id} self.mock_p_client.create_resource_provider.assert_called_with( resource_provider) self.mock_n_client.aggregates.create.assert_called_with(name, None) self.mock_p_client.associate_aggregates.assert_called_with( segment_id, [aggregate.uuid]) self.mock_n_client.aggregates.add_host.assert_called_with(aggregate.id, 'fakehost') total, reserved = self._calculate_inventory_total_and_reserved( subnet['subnet']) inventory, _ = self._get_inventory(total, reserved) ipv4_classname = seg_plugin.IPV4_RESOURCE_CLASS self.mock_p_client.update_resource_provider_inventories.\ assert_called_with(segment_id, {ipv4_classname: inventory}) self.assertEqual( inventory['total'], self.mock_p_client.update_resource_provider_inventories. call_args[0][1][ipv4_classname]['total']) self.assertEqual( inventory['reserved'], self.mock_p_client.update_resource_provider_inventories. call_args[0][1][ipv4_classname]['reserved']) self.mock_p_client.reset_mock() self.mock_p_client.get_inventory.side_effect = None self.mock_n_client.reset_mock() def _test_first_subnet_association_with_segment(self, cidr='10.0.0.0/24', allocation_pools=None): network, segment = self._create_test_network_and_segment() segment_id = segment['segment']['id'] self._setup_host_mappings([(segment_id, 'fakehost')]) self.mock_p_client.get_inventory.side_effect = ( placement_exc.PlacementResourceProviderNotFound( resource_provider=segment_id, resource_class=seg_plugin.IPV4_RESOURCE_CLASS)) aggregate = mock.MagicMock() aggregate.uuid = uuidutils.generate_uuid() aggregate.id = 1 self.mock_n_client.aggregates.create.return_value = aggregate subnet = self._create_test_subnet_with_segment( network, segment, cidr=cidr, allocation_pools=allocation_pools) self._assert_inventory_creation(segment_id, aggregate, subnet) return network, segment, subnet def test_first_subnet_association_with_segment(self): self._test_first_subnet_association_with_segment() def test_update_subnet_association_with_segment(self, cidr='10.0.0.0/24', allocation_pools=None): with self.network() as network: segment_id = self._list('segments')['segments'][0]['id'] network_id = network['network']['id'] self._setup_host_mappings([(segment_id, 'fakehost')]) self.mock_p_client.get_inventory.side_effect = ( placement_exc.PlacementResourceProviderNotFound( resource_provider=segment_id, resource_class=seg_plugin.IPV4_RESOURCE_CLASS)) aggregate = mock.MagicMock() aggregate.uuid = uuidutils.generate_uuid() aggregate.id = 1 self.mock_n_client.aggregates.create.return_value = aggregate ip_version = netaddr.IPNetwork(cidr).version with self.subnet(network=network, cidr=cidr, ip_version=ip_version, allocation_pools=allocation_pools, segment_id=None) as subnet: self._validate_l2_adjacency(network_id, is_adjacent=True) data = {'subnet': {'segment_id': segment_id}} self.new_update_request('subnets', data, subnet['subnet']['id']) self.new_update_request( 'subnets', data, subnet['subnet']['id']).get_response(self.api) self._validate_l2_adjacency(network_id, is_adjacent=False) self._assert_inventory_creation(segment_id, aggregate, subnet) def _assert_inventory_update(self, segment_id, inventory, subnet=None, original_subnet=None): self.batch_notifier._notify() self.mock_p_client.get_inventory.assert_called_with( segment_id, seg_plugin.IPV4_RESOURCE_CLASS) original_total = original_reserved = total = reserved = 0 if original_subnet: original_total, original_reserved = ( self._calculate_inventory_total_and_reserved(original_subnet)) if subnet: total, reserved = self._calculate_inventory_total_and_reserved( subnet) inventory['total'] += total - original_total inventory['reserved'] += reserved - original_reserved self.mock_p_client.update_resource_provider_inventory.\ assert_called_with(segment_id, inventory, seg_plugin.IPV4_RESOURCE_CLASS) self.assertEqual( inventory['total'], self.mock_p_client.update_resource_provider_inventory. call_args[0][1]['total']) self.assertEqual( inventory['reserved'], self.mock_p_client.update_resource_provider_inventory. call_args[0][1]['reserved']) self.mock_p_client.reset_mock() self.mock_n_client.reset_mock() def _get_inventory(self, total, reserved): inventory = {'total': total, 'reserved': reserved, 'min_unit': 1, 'max_unit': 1, 'step_size': 1, 'allocation_ratio': 1.0} return inventory, copy.deepcopy(inventory) def _test_second_subnet_association_with_segment(self): network, segment, first_subnet = ( self._test_first_subnet_association_with_segment()) segment_id = segment['segment']['id'] # Associate an IPv6 subnet with the segment self._create_test_subnet_with_segment(network, segment) first_total, first_reserved = ( self._calculate_inventory_total_and_reserved( first_subnet['subnet'])) inventory, original_inventory = self._get_inventory(first_total, first_reserved) self.mock_p_client.get_inventory.return_value = inventory second_subnet = self._create_test_subnet_with_segment( network, segment, cidr='10.0.1.0/24') self._assert_inventory_update(segment_id, original_inventory, subnet=second_subnet['subnet']) return segment_id, first_subnet, second_subnet def test_second_subnet_association_with_segment(self): self._test_second_subnet_association_with_segment() def test_delete_last_ipv4_subnet(self): network, segment, subnet = ( self._test_first_subnet_association_with_segment()) # Associate an IPv6 subnet with the segment self._create_test_subnet_with_segment(network, segment) segment_id = segment['segment']['id'] aggregate = mock.MagicMock() aggregate.uuid = uuidutils.generate_uuid() aggregate.id = 1 aggregate.hosts = ['fakehost1'] self.mock_p_client.list_aggregates.return_value = { 'aggregates': [aggregate.uuid]} self.mock_n_client.aggregates.list.return_value = [aggregate] self.mock_n_client.aggregates.get_details.return_value = aggregate self._delete('subnets', subnet['subnet']['id']) self.batch_notifier._notify() self._assert_inventory_delete(segment_id, aggregate) def _assert_inventory_delete(self, segment_id, aggregate): self.mock_p_client.list_aggregates.assert_called_with(segment_id) self.assertEqual(1, self.mock_n_client.aggregates.list.call_count) self.mock_n_client.aggregates.get_details.assert_called_with( aggregate.id) calls = [mock.call(aggregate.id, host) for host in aggregate.hosts] self.mock_n_client.aggregates.remove_host.assert_has_calls(calls) self.mock_n_client.aggregates.delete.assert_called_with(aggregate.id) self.mock_p_client.delete_resource_provider.assert_called_with( segment_id) self.mock_p_client.reset_mock() self.mock_n_client.reset_mock() def test_delete_ipv4_subnet(self): segment_id, first_subnet, second_subnet = ( self._test_second_subnet_association_with_segment()) first_total, first_reserved = ( self._calculate_inventory_total_and_reserved( first_subnet['subnet'])) second_total, second_reserved = ( self._calculate_inventory_total_and_reserved( second_subnet['subnet'])) inventory, original_inventory = self._get_inventory( first_total + second_total, first_reserved + second_reserved) self.mock_p_client.get_inventory.return_value = inventory self._delete('subnets', first_subnet['subnet']['id']) self._assert_inventory_update(segment_id, original_inventory, original_subnet=first_subnet['subnet']) def _test_update_ipv4_subnet_allocation_pools(self, allocation_pools, new_allocation_pools): network, segment, original_subnet = ( self._test_first_subnet_association_with_segment( cidr='10.0.0.0/24', allocation_pools=allocation_pools)) segment_id = segment['segment']['id'] self.mock_p_client.reset_mock() self.mock_n_client.reset_mock() total, reserved = self._calculate_inventory_total_and_reserved( original_subnet['subnet']) inventory, original_inventory = self._get_inventory(total, reserved) self.mock_p_client.get_inventory.return_value = inventory subnet_data = {'subnet': {'allocation_pools': new_allocation_pools}} subnet_req = self.new_update_request('subnets', subnet_data, original_subnet['subnet']['id']) subnet = self.deserialize(self.fmt, subnet_req.get_response(self.api)) self._assert_inventory_update( segment_id, original_inventory, subnet=subnet['subnet'], original_subnet=original_subnet['subnet']) def test_update_ipv4_subnet_expand_allocation_pool(self): self._test_update_ipv4_subnet_allocation_pools( [{'start': '10.0.0.2', 'end': '10.0.0.100'}], [{'start': '10.0.0.2', 'end': '10.0.0.254'}]) def test_update_ipv4_subnet_add_allocation_pool(self): self._test_update_ipv4_subnet_allocation_pools( [{'start': '10.0.0.2', 'end': '10.0.0.100'}], [{'start': '10.0.0.2', 'end': '10.0.0.100'}, {'start': '10.0.0.200', 'end': '10.0.0.254'}]) def test_update_ipv4_subnet_contract_allocation_pool(self): self._test_update_ipv4_subnet_allocation_pools( [{'start': '10.0.0.2', 'end': '10.0.0.254'}], [{'start': '10.0.0.2', 'end': '10.0.0.100'}]) def test_update_ipv4_subnet_remove_allocation_pool(self): self._test_update_ipv4_subnet_allocation_pools( [{'start': '10.0.0.2', 'end': '10.0.0.100'}, {'start': '10.0.0.200', 'end': '10.0.0.254'}], [{'start': '10.0.0.2', 'end': '10.0.0.100'}]) def _test_update_ipv4_subnet_delete_allocation_pools(self): segment_id, first_subnet, second_subnet = ( self._test_second_subnet_association_with_segment()) first_total, first_reserved = ( self._calculate_inventory_total_and_reserved( first_subnet['subnet'])) second_total, second_reserved = ( self._calculate_inventory_total_and_reserved( second_subnet['subnet'])) inventory, original_inventory = self._get_inventory( first_total + second_total, first_reserved + second_reserved) self.mock_p_client.get_inventory.return_value = inventory subnet_data = {'subnet': {'allocation_pools': []}} subnet_req = self.new_update_request('subnets', subnet_data, first_subnet['subnet']['id']) subnet_req.get_response(self.api) self._assert_inventory_update(segment_id, original_inventory, original_subnet=first_subnet['subnet']) return segment_id, second_subnet def test_update_ipv4_subnet_delete_allocation_pools(self): self._test_update_ipv4_subnet_delete_allocation_pools() def test_update_ipv4_subnet_delete_restore_last_allocation_pool(self): segment_id, subnet = ( self._test_update_ipv4_subnet_delete_allocation_pools()) self.mock_p_client.reset_mock() self.mock_n_client.reset_mock() allocation_pools = subnet['subnet']['allocation_pools'] aggregate = mock.MagicMock() aggregate.uuid = uuidutils.generate_uuid() aggregate.id = 1 aggregate.hosts = ['fakehost1'] self.mock_p_client.list_aggregates.return_value = { 'aggregates': [aggregate.uuid]} self.mock_n_client.aggregates.list.return_value = [aggregate] self.mock_n_client.aggregates.get_details.return_value = aggregate subnet_data = {'subnet': {'allocation_pools': []}} self._update('subnets', subnet['subnet']['id'], subnet_data) self.batch_notifier._notify() self._assert_inventory_delete(segment_id, aggregate) self.mock_p_client.get_inventory.side_effect = ( placement_exc.PlacementResourceProviderNotFound( resource_provider=segment_id, resource_class=seg_plugin.IPV4_RESOURCE_CLASS)) aggregate.hosts = [] self.mock_n_client.aggregates.create.return_value = aggregate subnet_data = {'subnet': {'allocation_pools': allocation_pools}} subnet = self._update('subnets', subnet['subnet']['id'], subnet_data) self._assert_inventory_creation(segment_id, aggregate, subnet) def test_add_host_to_segment_aggregate(self): db.subscribe() network, segment, first_subnet = ( self._test_first_subnet_association_with_segment()) segment_id = segment['segment']['id'] aggregate = mock.MagicMock() aggregate.uuid = uuidutils.generate_uuid() aggregate.id = 1 aggregate.hosts = ['fakehost1'] self.mock_p_client.list_aggregates.return_value = { 'aggregates': [aggregate.uuid]} self.mock_n_client.aggregates.list.return_value = [aggregate] host = 'otherfakehost' helpers.register_ovs_agent(host=host, bridge_mappings={'physnet': 'br-eth-1'}, plugin=self.plugin, start_flag=True) self.batch_notifier._notify() self.mock_p_client.list_aggregates.assert_called_with(segment_id) self.assertEqual(1, self.mock_n_client.aggregates.list.call_count) self.mock_n_client.aggregates.add_host.assert_called_with(aggregate.id, host) def test_add_host_to_non_existent_segment_aggregate(self): self._mock_keystone_auth() db.subscribe() network, segment, first_subnet = ( self._test_first_subnet_association_with_segment()) with mock.patch.object(seg_plugin.LOG, 'info') as log: segment_id = segment['segment']['id'] aggregate = mock.MagicMock() aggregate.uuid = uuidutils.generate_uuid() aggregate.id = 1 aggregate.hosts = ['fakehost1'] self.mock_p_client.list_aggregates.side_effect = ( placement_exc.PlacementAggregateNotFound( resource_provider=segment_id)) self.mock_n_client.aggregates.list.return_value = [aggregate] host = 'otherfakehost' helpers.register_ovs_agent(host=host, bridge_mappings={'physnet': 'br-eth-1'}, plugin=self.plugin, start_flag=True) self.batch_notifier._notify() self.mock_p_client.list_aggregates.assert_called_with(segment_id) self.assertTrue(log.called) self.mock_n_client.aggregates.add_host.assert_not_called() def test_add_host_segment_aggregate_conflict(self): db.subscribe() network, segment, first_subnet = ( self._test_first_subnet_association_with_segment()) with mock.patch.object(seg_plugin.LOG, 'info') as log: segment_id = segment['segment']['id'] aggregate = mock.MagicMock() aggregate.uuid = uuidutils.generate_uuid() aggregate.id = 1 aggregate.hosts = ['fakehost1'] self.mock_p_client.list_aggregates.return_value = { 'aggregates': [aggregate.uuid]} self.mock_n_client.aggregates.add_host.side_effect = ( nova_exc.Conflict(nova_exc.Conflict.http_status)) self.mock_n_client.aggregates.list.return_value = [aggregate] host = 'otherfakehost' helpers.register_ovs_agent(host=host, bridge_mappings={'physnet': 'br-eth-1'}, plugin=self.plugin, start_flag=True) self.batch_notifier._notify() self.mock_p_client.list_aggregates.assert_called_with(segment_id) self.mock_n_client.aggregates.add_host.assert_called_with( aggregate.id, host) self.assertTrue(log.called) def _assert_inventory_update_port(self, segment_id, inventory, num_fixed_ips): inventory['reserved'] += num_fixed_ips self.mock_p_client.get_inventory.assert_called_with( segment_id, seg_plugin.IPV4_RESOURCE_CLASS) self.mock_p_client.update_resource_provider_inventory.\ assert_called_with(segment_id, inventory, seg_plugin.IPV4_RESOURCE_CLASS) self.assertEqual( inventory['total'], self.mock_p_client.update_resource_provider_inventory. call_args[0][1]['total']) self.assertEqual( inventory['reserved'], self.mock_p_client.update_resource_provider_inventory. call_args[0][1]['reserved']) self.mock_p_client.reset_mock() self.mock_n_client.reset_mock() def _create_test_port(self, network_id, tenant_id, subnet, **kwargs): port = self._make_port(self.fmt, network_id, tenant_id=tenant_id, arg_list=(portbindings.HOST_ID,), **kwargs) self.batch_notifier._notify() return port def _test_create_port(self, **kwargs): network, segment, subnet = ( self._test_first_subnet_association_with_segment()) total, reserved = self._calculate_inventory_total_and_reserved( subnet['subnet']) inventory, original_inventory = self._get_inventory(total, reserved) self.mock_p_client.get_inventory.return_value = inventory port = self._create_test_port(network['network']['id'], network['network']['tenant_id'], subnet, **kwargs) return segment['segment']['id'], original_inventory, port def test_create_bound_port(self): kwargs = {portbindings.HOST_ID: 'fakehost'} segment_id, original_inventory, _ = self._test_create_port(**kwargs) self._assert_inventory_update_port(segment_id, original_inventory, 1) def test_create_bound_port_compute_owned(self): kwargs = {portbindings.HOST_ID: 'fakehost', 'device_owner': constants.DEVICE_OWNER_COMPUTE_PREFIX} self._test_create_port(**kwargs) self.mock_p_client.get_inventory.assert_not_called() self.mock_p_client.update_resource_provider_inventory.\ assert_not_called() def test_create_bound_port_dhcp_owned(self): kwargs = {portbindings.HOST_ID: 'fakehost', 'device_owner': constants.DEVICE_OWNER_DHCP} self._test_create_port(**kwargs) self.mock_p_client.get_inventory.assert_not_called() self.mock_p_client.update_resource_provider_inventory.\ assert_not_called() def test_create_unbound_port(self): self._test_create_port() self.mock_p_client.get_inventory.assert_not_called() self.mock_p_client.update_resource_provider_inventory.\ assert_not_called() def test_delete_bound_port(self): kwargs = {portbindings.HOST_ID: 'fakehost'} segment_id, before_create_inventory, port = self._test_create_port( **kwargs) self.mock_p_client.reset_mock() inventory, original_inventory = self._get_inventory( before_create_inventory['total'], before_create_inventory['reserved'] + 1) self.mock_p_client.get_inventory.return_value = inventory self._delete('ports', port['port']['id']) self.batch_notifier._notify() self._assert_inventory_update_port(segment_id, original_inventory, -1) def _create_port_for_update_test(self, num_fixed_ips=1, dhcp_owned=False, compute_owned=False): segment_id, first_subnet, second_subnet = ( self._test_second_subnet_association_with_segment()) first_total, first_reserved = ( self._calculate_inventory_total_and_reserved( first_subnet['subnet'])) second_total, second_reserved = ( self._calculate_inventory_total_and_reserved( second_subnet['subnet'])) inventory, original_inventory = self._get_inventory( first_total + second_total, first_reserved + second_reserved) self.mock_p_client.get_inventory.return_value = inventory kwargs = {portbindings.HOST_ID: 'fakehost', 'fixed_ips': [{'subnet_id': first_subnet['subnet']['id']}]} created_fixed_ips = num_fixed_ips if num_fixed_ips > 1: kwargs['fixed_ips'].append( {'subnet_id': second_subnet['subnet']['id']}) if dhcp_owned: kwargs['device_owner'] = constants.DEVICE_OWNER_DHCP if compute_owned: kwargs['device_owner'] = constants.DEVICE_OWNER_COMPUTE_PREFIX port = self._create_test_port(first_subnet['subnet']['network_id'], first_subnet['subnet']['tenant_id'], first_subnet, **kwargs) if dhcp_owned or compute_owned: self.mock_p_client.get_inventory.assert_not_called() self.mock_p_client.update_resource_provider_inventory.\ assert_not_called() else: self._assert_inventory_update_port(segment_id, original_inventory, created_fixed_ips) return first_subnet, second_subnet, port def _port_update(self, first_subnet, second_subnet, fixed_ips_subnets, port, reserved_increment_before=1, reserved_increment_after=1, dhcp_owned=False, compute_owned=False): first_total, first_reserved = ( self._calculate_inventory_total_and_reserved( first_subnet['subnet'])) second_total, second_reserved = ( self._calculate_inventory_total_and_reserved( second_subnet['subnet'])) inventory, original_inventory = self._get_inventory( first_total + second_total, first_reserved + second_reserved + reserved_increment_before) self.mock_p_client.get_inventory.return_value = inventory port_data = {'port': {'device_owner': ''}} if fixed_ips_subnets: port_data['port']['fixed_ips'] = [] for subnet in fixed_ips_subnets: port_data['port']['fixed_ips'].append( {'subnet_id': subnet['subnet']['id']}) if dhcp_owned: port_data['port']['device_owner'] = constants.DEVICE_OWNER_DHCP if compute_owned: port_data['port']['device_owner'] = ( constants.DEVICE_OWNER_COMPUTE_PREFIX) self._update('ports', port['port']['id'], port_data) self.batch_notifier._notify() self._assert_inventory_update_port( first_subnet['subnet']['segment_id'], original_inventory, reserved_increment_after) def test_update_port_add_fixed_ip(self): first_subnet, second_subnet, port = self._create_port_for_update_test() self._port_update(first_subnet, second_subnet, [first_subnet, second_subnet], port) def test_update_port_remove_fixed_ip(self): first_subnet, second_subnet, port = self._create_port_for_update_test( num_fixed_ips=2) self._port_update(first_subnet, second_subnet, [first_subnet], port, reserved_increment_before=2, reserved_increment_after=-1) def test_update_port_change_to_dhcp_owned(self): first_subnet, second_subnet, port = self._create_port_for_update_test() self._port_update(first_subnet, second_subnet, [], port, reserved_increment_after=-1, dhcp_owned=True) def test_update_port_change_to_no_dhcp_owned(self): first_subnet, second_subnet, port = self._create_port_for_update_test( dhcp_owned=True) self._port_update(first_subnet, second_subnet, [], port, reserved_increment_before=0, reserved_increment_after=1) def test_update_port_change_to_compute_owned(self): first_subnet, second_subnet, port = self._create_port_for_update_test() self._port_update(first_subnet, second_subnet, [], port, reserved_increment_after=-1, compute_owned=True) def test_update_port_change_to_no_compute_owned(self): first_subnet, second_subnet, port = self._create_port_for_update_test( compute_owned=True) self._port_update(first_subnet, second_subnet, [], port, reserved_increment_before=0, reserved_increment_after=1) def test_placement_api_inventory_update_conflict(self): with mock.patch.object(seg_plugin.LOG, 'debug') as log_debug: with mock.patch.object(seg_plugin.LOG, 'error') as log_error: event = seg_plugin.Event(mock.ANY, mock.ANY, total=1, reserved=0) inventory, original_inventory = self._get_inventory(100, 2) self.mock_p_client.get_inventory.return_value = inventory self.mock_p_client.update_resource_provider_inventory.\ side_effect = ( placement_exc. PlacementResourceProviderGenerationConflict( resource_provider=mock.ANY, generation=1)) self.segments_plugin.nova_updater._update_nova_inventory(event) self.assertEqual(seg_plugin.MAX_INVENTORY_UPDATE_RETRIES, self.mock_p_client.get_inventory.call_count) self.assertEqual( seg_plugin.MAX_INVENTORY_UPDATE_RETRIES, self.mock_p_client.update_resource_provider_inventory. call_count) self.assertEqual( seg_plugin.MAX_INVENTORY_UPDATE_RETRIES, log_debug.call_count) self.assertTrue(log_error.called) def test_placement_api_not_available(self): with mock.patch.object(seg_plugin.LOG, 'debug') as log: event = seg_plugin.Event( self.segments_plugin.nova_updater._update_nova_inventory, mock.ANY, total=1, reserved=0) self.mock_p_client.get_inventory.side_effect = ( placement_exc.PlacementEndpointNotFound()) self.segments_plugin.nova_updater._send_notifications([event]) self.assertTrue(log.called) def _test_create_network_and_segment(self, phys_net): with self.network() as net: network = net['network'] segment = self._test_create_segment( network_id=network['id'], physical_network=phys_net, segmentation_id=200, network_type='vlan') return network, segment['segment'] def test_delete_network_and_owned_segments(self): db.subscribe() aggregate = mock.MagicMock() aggregate.uuid = uuidutils.generate_uuid() aggregate.id = 1 aggregate.hosts = ['fakehost1'] self.mock_p_client.list_aggregates.return_value = { 'aggregates': [aggregate.uuid]} self.mock_n_client.aggregates.list.return_value = [aggregate] self.mock_n_client.aggregates.get_details.return_value = aggregate network, segment = self._test_create_network_and_segment('physnet') self._delete('networks', network['id']) self.mock_n_client.aggregates.remove_host.assert_has_calls( [mock.call(aggregate.id, 'fakehost1')]) self.mock_n_client.aggregates.delete.assert_has_calls( [mock.call(aggregate.id)]) self.mock_p_client.delete_resource_provider.assert_has_calls( [mock.call(segment['id'])]) class TestDhcpAgentSegmentScheduling(HostSegmentMappingTestCase): _mechanism_drivers = ['openvswitch', 'logger'] mock_path = 'neutron.services.segments.db.update_segment_host_mapping' block_dhcp_notifier = False def setUp(self): super(TestDhcpAgentSegmentScheduling, self).setUp() self.dhcp_agent_db = agentschedulers_db.DhcpAgentSchedulerDbMixin() self.ctx = context.get_admin_context() def _test_create_network_and_segment(self, phys_net): with self.network() as net: network = net['network'] segment = self._test_create_segment(network_id=network['id'], physical_network=phys_net, segmentation_id=200, network_type='vlan') dhcp_agents = self.dhcp_agent_db.get_dhcp_agents_hosting_networks( self.ctx, [network['id']]) self.assertEqual(0, len(dhcp_agents)) return network, segment['segment'] def _test_create_subnet(self, network, segment, cidr=None, enable_dhcp=True): cidr = cidr or '10.0.0.0/24' ip_version = constants.IP_VERSION_4 with self.subnet(network={'network': network}, segment_id=segment['id'], ip_version=ip_version, cidr=cidr, enable_dhcp=enable_dhcp) as subnet: pass return subnet['subnet'] def _register_dhcp_agents(self, hosts=None): hosts = hosts or [DHCP_HOSTA, DHCP_HOSTB] for host in hosts: helpers.register_dhcp_agent(host) def test_network_scheduling_on_segment_creation(self): self._register_dhcp_agents() self._test_create_network_and_segment('phys_net1') def test_segment_scheduling_no_host_mapping(self): self._register_dhcp_agents() network, segment = self._test_create_network_and_segment('phys_net1') self._test_create_subnet(network, segment) dhcp_agents = self.dhcp_agent_db.get_dhcp_agents_hosting_networks( self.ctx, [network['id']]) self.assertEqual(0, len(dhcp_agents)) def test_segment_scheduling_with_host_mapping(self): phys_net1 = 'phys_net1' self._register_dhcp_agents() network, segment = self._test_create_network_and_segment(phys_net1) self._register_agent(DHCP_HOSTA, mappings={phys_net1: 'br-eth-1'}, plugin=self.plugin) self._test_create_subnet(network, segment) dhcp_agents = self.dhcp_agent_db.get_dhcp_agents_hosting_networks( self.ctx, [network['id']]) self.assertEqual(1, len(dhcp_agents)) self.assertEqual(DHCP_HOSTA, dhcp_agents[0]['host']) def test_segment_scheduling_with_multiple_host_mappings(self): phys_net1 = 'phys_net1' phys_net2 = 'phys_net2' self._register_dhcp_agents([DHCP_HOSTA, DHCP_HOSTB, 'MEHA', 'MEHB']) network, segment1 = self._test_create_network_and_segment(phys_net1) segment2 = self._test_create_segment(network_id=network['id'], physical_network=phys_net2, segmentation_id=200, network_type='vlan')['segment'] self._register_agent(DHCP_HOSTA, mappings={phys_net1: 'br-eth-1'}, plugin=self.plugin) self._register_agent(DHCP_HOSTB, mappings={phys_net2: 'br-eth-1'}, plugin=self.plugin) self._test_create_subnet(network, segment1) self._test_create_subnet(network, segment2, cidr='11.0.0.0/24') dhcp_agents = self.dhcp_agent_db.get_dhcp_agents_hosting_networks( self.ctx, [network['id']]) self.assertEqual(2, len(dhcp_agents)) agent_hosts = [agent['host'] for agent in dhcp_agents] self.assertIn(DHCP_HOSTA, agent_hosts) self.assertIn(DHCP_HOSTB, agent_hosts) class TestSegmentHostRoutes(TestSegmentML2): VLAN_MIN = 200 VLAN_MAX = 209 def setUp(self): # NOTE(mlavalle): ml2_type_vlan requires to be registered before used. # This piece was refactored and removed from .config, so it causes # a problem, when tests are executed with pdb. # There is no problem when tests are running without debugger. driver_type.register_ml2_drivers_vlan_opts() cfg.CONF.set_override( 'network_vlan_ranges', ['physnet:%s:%s' % (self.VLAN_MIN, self.VLAN_MAX), 'physnet0:%s:%s' % (self.VLAN_MIN, self.VLAN_MAX), 'physnet1:%s:%s' % (self.VLAN_MIN, self.VLAN_MAX), 'physnet2:%s:%s' % (self.VLAN_MIN, self.VLAN_MAX)], group='ml2_type_vlan') super(TestSegmentHostRoutes, self).setUp() def _create_subnets_segments(self, gateway_ips, cidrs): with self.network() as network: net = network['network'] segment0 = self._test_create_segment( network_id=net['id'], physical_network='physnet1', network_type=constants.TYPE_VLAN, segmentation_id=201)['segment'] segment1 = self._test_create_segment( network_id=net['id'], physical_network='physnet2', network_type=constants.TYPE_VLAN, segmentation_id=202)['segment'] with self.subnet(network=network, segment_id=segment0['id'], gateway_ip=gateway_ips[0], cidr=cidrs[0]) as subnet0, \ self.subnet(network=network, segment_id=segment1['id'], gateway_ip=gateway_ips[1], cidr=cidrs[1]) as subnet1: pass return net, subnet0['subnet'], subnet1['subnet'] def test_host_routes_two_subnets_with_segments_association(self): """Creates two subnets associated to different segments. Since the two subnets are associated with different segments on the same network host routes will be created. """ gateway_ips = ['10.0.1.1', '10.0.2.1'] cidrs = ['10.0.1.0/24', '10.0.2.0/24'] host_routes = [{'destination': cidrs[1], 'nexthop': gateway_ips[0]}, {'destination': cidrs[0], 'nexthop': gateway_ips[1]}] net, subnet0, subnet1 = self._create_subnets_segments(gateway_ips, cidrs) net_req = self.new_show_request('networks', net['id']) raw_res = net_req.get_response(self.api) net_res = self.deserialize(self.fmt, raw_res) for subnet_id in net_res['network']['subnets']: sub_req = self.new_show_request('subnets', subnet_id) raw_res = sub_req.get_response(self.api) sub_res = self.deserialize(self.fmt, raw_res)['subnet'] self.assertIn(sub_res['cidr'], cidrs) self.assertIn(sub_res['gateway_ip'], gateway_ips) self.assertIn(sub_res['host_routes'][0], host_routes) def test_host_routes_two_subnets_with_same_segment_association(self): """Creates two subnets associated to the same segment. Since the two subnets are both associated with the same segment no host routes will be created. """ gateway_ips = ['10.0.1.1', '10.0.2.1'] cidrs = ['10.0.1.0/24', '10.0.2.0/24'] with self.network() as network: net = network['network'] segment = self._test_create_segment( network_id=net['id'], physical_network='physnet1', network_type=constants.TYPE_VLAN, segmentation_id=201)['segment'] with self.subnet(network=network, segment_id=segment['id'], gateway_ip=gateway_ips[0], cidr=cidrs[0]) as subnet0, \ self.subnet(network=network, segment_id=segment['id'], gateway_ip=gateway_ips[1], cidr=cidrs[1]) as subnet1: subnet0 = subnet0['subnet'] subnet1 = subnet1['subnet'] req = self.new_show_request('subnets', subnet0['id']) res = req.get_response(self.api) res_subnet0 = self.deserialize(self.fmt, res) req = self.new_show_request('subnets', subnet1['id']) res = req.get_response(self.api) res_subnet1 = self.deserialize(self.fmt, res) self.assertEqual([], res_subnet0['subnet']['host_routes']) self.assertEqual([], res_subnet1['subnet']['host_routes']) def test_host_routes_create_two_subnets_then_delete_one(self): """Delete subnet after creating two subnets associated same segment. Host routes with destination to the subnet that is deleted are removed from the remaining subnets. """ gateway_ips = ['10.0.1.1', '10.0.2.1'] cidrs = ['10.0.1.0/24', '10.0.2.0/24'] net, subnet0, subnet1 = self._create_subnets_segments(gateway_ips, cidrs) sh_req = self.new_show_request('subnets', subnet1['id']) raw_res = sh_req.get_response(self.api) sub_res = self.deserialize(self.fmt, raw_res) self.assertEqual([{'destination': cidrs[0], 'nexthop': gateway_ips[1]}], sub_res['subnet']['host_routes']) del_req = self.new_delete_request('subnets', subnet0['id']) del_req.get_response(self.api) sh_req = self.new_show_request('subnets', subnet1['id']) raw_res = sh_req.get_response(self.api) sub_res = self.deserialize(self.fmt, raw_res) self.assertEqual([], sub_res['subnet']['host_routes']) def test_host_routes_two_subnets_then_change_gateway_ip(self): gateway_ips = ['10.0.1.1', '10.0.2.1'] cidrs = ['10.0.1.0/24', '10.0.2.0/24'] host_routes = [{'destination': cidrs[1], 'nexthop': gateway_ips[0]}, {'destination': cidrs[0], 'nexthop': gateway_ips[1]}] net, subnet0, subnet1 = self._create_subnets_segments(gateway_ips, cidrs) net_req = self.new_show_request('networks', net['id']) raw_res = net_req.get_response(self.api) net_res = self.deserialize(self.fmt, raw_res) for subnet_id in net_res['network']['subnets']: sub_req = self.new_show_request('subnets', subnet_id) raw_res = sub_req.get_response(self.api) sub_res = self.deserialize(self.fmt, raw_res)['subnet'] self.assertIn(sub_res['cidr'], cidrs) self.assertIn(sub_res['gateway_ip'], gateway_ips) self.assertIn(sub_res['host_routes'][0], host_routes) new_gateway_ip = '10.0.1.254' data = {'subnet': {'gateway_ip': new_gateway_ip, 'allocation_pools': [{'start': '10.0.1.1', 'end': '10.0.1.253'}]}} self.new_update_request( 'subnets', data, subnet0['id']).get_response(self.api) sh_req = self.new_show_request('subnets', subnet0['id']) raw_res = sh_req.get_response(self.api) sub_res = self.deserialize(self.fmt, raw_res) self.assertEqual([{'destination': cidrs[1], 'nexthop': new_gateway_ip}], sub_res['subnet']['host_routes']) def test_host_routes_two_subnets_summary_route_in_request(self): gateway_ips = ['10.0.1.1', '10.0.2.1'] cidrs = ['10.0.1.0/24', '10.0.2.0/24'] summary_net = '10.0.0.0/16' host_routes = [{'destination': summary_net, 'nexthop': gateway_ips[0]}, {'destination': summary_net, 'nexthop': gateway_ips[1]}] with self.network() as network: net = network['network'] segment0 = self._test_create_segment( network_id=net['id'], physical_network='physnet1', network_type=constants.TYPE_VLAN, segmentation_id=201)['segment'] segment1 = self._test_create_segment( network_id=net['id'], physical_network='physnet2', network_type=constants.TYPE_VLAN, segmentation_id=202)['segment'] self.subnet(network=network, segment_id=segment0['id'], gateway_ip=gateway_ips[0], cidr=cidrs[0], host_routes=[host_routes[0]]) self.subnet(network=network, segment_id=segment1['id'], gateway_ip=gateway_ips[1], cidr=cidrs[1], host_routes=[host_routes[1]]) net_req = self.new_show_request('networks', net['id']) raw_res = net_req.get_response(self.api) net_res = self.deserialize(self.fmt, raw_res) for subnet_id in net_res['network']['subnets']: sub_req = self.new_show_request('subnets', subnet_id) raw_res = sub_req.get_response(self.api) sub_res = self.deserialize(self.fmt, raw_res)['subnet'] self.assertIn(sub_res['cidr'], cidrs) self.assertIn(sub_res['gateway_ip'], gateway_ips) self.assertEqual(len(sub_res['host_routes']), 1) self.assertIn(sub_res['host_routes'][0], host_routes) class TestSegmentHostMappingNoStore( test_db_base_plugin_v2.NeutronDbPluginV2TestCase): def setUp(self): driver_type.register_ml2_drivers_vlan_opts() cfg.CONF.set_override('network_vlan_ranges', ['phys_net1'], group='ml2_type_vlan') cfg.CONF.set_override('service_plugins', []) super(TestSegmentHostMappingNoStore, self).setUp( plugin='neutron.plugins.ml2.plugin.Ml2Plugin') # set to None for simulating server start db._USER_CONFIGURED_SEGMENT_PLUGIN = None db.subscribe() @mock.patch('neutron.services.segments.db.update_segment_host_mapping') @mock.patch('neutron.services.segments.db.map_segment_to_hosts') def test_no_segmenthostmapping_when_disable_segment( self, mock_map_segment_to_hosts, mock_update_segment_mapping): with self.network( arg_list=('provider:network_type', 'provider:physical_network', 'provider:segmentation_id'), **{'provider:network_type': 'vlan', 'provider:physical_network': 'phys_net1', 'provider:segmentation_id': '400'}) as network: network['network'] mock_map_segment_to_hosts.assert_not_called() host1 = 'test_host' physical_network = 'phys_net1' helpers.register_ovs_agent( host=host1, bridge_mappings={physical_network: 'br-eth-1'}, plugin=self.plugin) mock_update_segment_mapping.assert_not_called()