From 996b84540b87f3becf21e1477c6b132684e41156 Mon Sep 17 00:00:00 2001 From: Aradhana Singh Date: Tue, 28 Jun 2016 00:07:06 +0000 Subject: [PATCH] DHCP Auto Scheduling for routed provider networks In routed provider networks, a network can be divided by segments and each segment will have its own DHCP agent. AutoScheduling for networks exists. This patchset is for enabling auto scheduling for routed networks. Change-Id: I7d9f01dbd6d9c216474ee47c968919c1826787f7 Partially-implements: blueprint routed-networks --- neutron/scheduler/dhcp_agent_scheduler.py | 30 ++++- .../scheduler/test_dhcp_agent_scheduler.py | 3 +- .../scheduler/test_dhcp_agent_scheduler.py | 114 +++++++++++++++++- 3 files changed, 137 insertions(+), 10 deletions(-) diff --git a/neutron/scheduler/dhcp_agent_scheduler.py b/neutron/scheduler/dhcp_agent_scheduler.py index 7b3d66002b9..753baabb1b6 100644 --- a/neutron/scheduler/dhcp_agent_scheduler.py +++ b/neutron/scheduler/dhcp_agent_scheduler.py @@ -30,6 +30,7 @@ from neutron.db.network_dhcp_agent_binding import models as ndab_model from neutron.extensions import availability_zone as az_ext from neutron.scheduler import base_resource_filter from neutron.scheduler import base_scheduler +from neutron.services.segments import db as segments_db LOG = logging.getLogger(__name__) @@ -44,10 +45,16 @@ class AutoScheduler(object): # a list of (agent, net_ids) tuples bindings_to_add = [] with context.session.begin(subtransactions=True): - fields = ['network_id', 'enable_dhcp'] + fields = ['network_id', 'enable_dhcp', 'segment_id'] subnets = plugin.get_subnets(context, fields=fields) - net_ids = set(s['network_id'] for s in subnets - if s['enable_dhcp']) + net_ids = {} + net_segment_ids = collections.defaultdict(set) + for s in subnets: + if s['enable_dhcp']: + net_segment_ids[s['network_id']].add(s.get('segment_id')) + for network_id, segment_ids in net_segment_ids.items(): + is_routed_network = any(segment_ids) + net_ids[network_id] = is_routed_network if not net_ids: LOG.debug('No non-hosted networks') return False @@ -57,17 +64,28 @@ class AutoScheduler(object): agents_db.Agent.host == host, agents_db.Agent.admin_state_up == sql.true()) dhcp_agents = query.all() + + query = context.session.query( + segments_db.SegmentHostMapping.segment_id) + query = query.filter(segments_db.SegmentHostMapping.host == host) + segments_on_host = {s.segment_id for s in query} + for dhcp_agent in dhcp_agents: if agents_db.AgentDbMixin.is_agent_down( dhcp_agent.heartbeat_timestamp): LOG.warning(_LW('DHCP agent %s is not active'), dhcp_agent.id) continue - for net_id in net_ids: + for net_id, is_routed_network in net_ids.items(): agents = plugin.get_dhcp_agents_hosting_networks( context, [net_id]) - if len(agents) >= agents_per_network: - continue + segments_on_network = net_segment_ids[net_id] + if is_routed_network: + if len(segments_on_network & segments_on_host) == 0: + continue + else: + if len(agents) >= agents_per_network: + continue if any(dhcp_agent.id == agent.id for agent in agents): continue net = plugin.get_network(context, net_id) diff --git a/neutron/tests/functional/scheduler/test_dhcp_agent_scheduler.py b/neutron/tests/functional/scheduler/test_dhcp_agent_scheduler.py index 544754e8073..5cef44f7d46 100644 --- a/neutron/tests/functional/scheduler/test_dhcp_agent_scheduler.py +++ b/neutron/tests/functional/scheduler/test_dhcp_agent_scheduler.py @@ -364,7 +364,8 @@ class TestAutoSchedule(test_dhcp_sch.TestDhcpSchedulerBaseTestCase, enable_dhcp = (not self._strip_host_index(net_id) in self.networks_with_dhcp_disabled) subnets.append({'network_id': net_id, - 'enable_dhcp': enable_dhcp}) + 'enable_dhcp': enable_dhcp, + 'segment_id': None}) return subnets def get_network(self, context, net_id): diff --git a/neutron/tests/unit/scheduler/test_dhcp_agent_scheduler.py b/neutron/tests/unit/scheduler/test_dhcp_agent_scheduler.py index c4ed58a846f..a4f2adb6dfb 100644 --- a/neutron/tests/unit/scheduler/test_dhcp_agent_scheduler.py +++ b/neutron/tests/unit/scheduler/test_dhcp_agent_scheduler.py @@ -143,7 +143,8 @@ class TestDhcpScheduler(TestDhcpSchedulerBaseTestCase): self._test_get_agents_and_scheduler_for_dead_agent()) plugin = mock.Mock() plugin.get_subnets.return_value = [{"network_id": self.network_id, - "enable_dhcp": True}] + "enable_dhcp": True, + "segment_id": None}] plugin.get_network.return_value = self.network if active_hosts_only: plugin.get_dhcp_agents_hosting_networks.return_value = [] @@ -264,8 +265,8 @@ class TestAutoScheduleNetworks(TestDhcpSchedulerBaseTestCase): def test_auto_schedule_network(self): plugin = mock.MagicMock() plugin.get_subnets.return_value = ( - [{"network_id": self.network_id, "enable_dhcp": self.enable_dhcp}] - if self.network_present else []) + [{"network_id": self.network_id, "enable_dhcp": self.enable_dhcp, + "segment_id": None}] if self.network_present else []) plugin.get_network.return_value = {'availability_zone_hints': self.az_hints} scheduler = dhcp_agent_scheduler.ChanceScheduler() @@ -291,6 +292,113 @@ class TestAutoScheduleNetworks(TestDhcpSchedulerBaseTestCase): self.assertEqual(expected_hosted_agents, len(hosted_agents)) +class TestAutoScheduleSegments(test_plugin.Ml2PluginV2TestCase, + TestDhcpSchedulerBaseTestCase): + """Unit test scenarios for ChanceScheduler""" + + def setUp(self): + super(TestAutoScheduleSegments, self).setUp() + self.plugin = importutils.import_object('neutron.plugins.ml2.plugin.' + 'Ml2Plugin') + self.segments_plugin = importutils.import_object( + 'neutron.services.segments.plugin.Plugin') + self.ctx = context.get_admin_context() + + def _create_network(self): + net = self.plugin.create_network( + self.ctx, + {'network': {'name': 'name', + 'tenant_id': 'tenant_one', + 'admin_state_up': True, + 'shared': True}}) + return net['id'] + + def _create_segment(self, network_id): + seg = self.segments_plugin.create_segment( + self.ctx, + {'segment': {'network_id': network_id, + 'physical_network': constants.ATTR_NOT_SPECIFIED, + 'network_type': 'meh', + 'segmentation_id': constants.ATTR_NOT_SPECIFIED}}) + return seg['id'] + + def _create_subnet(self, segment_id, network_id, cidr='192.168.10.0/24'): + subnet = self.plugin.create_subnet( + self.ctx, + {'subnet': {'name': 'name', + 'ip_version': 4, + 'network_id': network_id, + 'cidr': cidr, + 'gateway_ip': constants.ATTR_NOT_SPECIFIED, + 'allocation_pools': constants.ATTR_NOT_SPECIFIED, + 'dns_nameservers': constants.ATTR_NOT_SPECIFIED, + 'host_routes': constants.ATTR_NOT_SPECIFIED, + 'tenant_id': 'tenant_one', + 'enable_dhcp': True, + 'segment_id': segment_id}}) + return subnet['id'] + + def test_auto_schedule_one_network_one_segment_one_subnet(self): + net_id = self._create_network() + seg_id = self._create_segment(net_id) + self._create_subnet(seg_id, net_id) + helpers.register_dhcp_agent(HOST_C) + segments_service_db.update_segment_host_mapping( + self.ctx, HOST_C, {seg_id}) + scheduler = dhcp_agent_scheduler.ChanceScheduler() + observed_return_val = scheduler.auto_schedule_networks( + self.plugin, self.ctx, HOST_C) + self.assertTrue(observed_return_val) + agent1 = self.plugin.get_dhcp_agents_hosting_networks( + self.ctx, [net_id]) + self.assertEqual(1, len(agent1)) + self.assertEqual('host-c', agent1[0]['host']) + + def test_auto_schedule_one_network_one_segment_two_subnet(self): + net_id = self._create_network() + seg_id = self._create_segment(net_id) + self._create_subnet(seg_id, net_id) + self._create_subnet(seg_id, net_id, '192.168.11.0/24') + helpers.register_dhcp_agent(HOST_C) + segments_service_db.update_segment_host_mapping( + self.ctx, HOST_C, {seg_id}) + scheduler = dhcp_agent_scheduler.ChanceScheduler() + observed_return_val = scheduler.auto_schedule_networks( + self.plugin, self.ctx, HOST_C) + self.assertTrue(observed_return_val) + agent1 = self.plugin.get_dhcp_agents_hosting_networks( + self.ctx, [net_id]) + self.assertEqual(1, len(agent1)) + self.assertEqual('host-c', agent1[0]['host']) + + def test_auto_schedule_one_network_two_segments_with_one_subnet_each(self): + net_id = self._create_network() + seg1_id = self._create_segment(net_id) + self._create_subnet(seg1_id, net_id) + helpers.register_dhcp_agent(HOST_D) + segments_service_db.update_segment_host_mapping( + self.ctx, HOST_D, {seg1_id}) + scheduler = dhcp_agent_scheduler.ChanceScheduler() + observed_val_first_segment = scheduler.auto_schedule_networks( + self.plugin, self.ctx, HOST_D) + self.assertTrue(observed_val_first_segment) + agents = self.plugin.get_dhcp_agents_hosting_networks( + self.ctx, [net_id]) + self.assertEqual(1, len(agents)) + + seg2_id = self._create_segment(net_id) + self._create_subnet(seg2_id, net_id, '192.168.11.0/24') + helpers.register_dhcp_agent(HOST_C) + segments_service_db.update_segment_host_mapping( + self.ctx, HOST_C, {seg2_id}) + observed_val_second_segment = scheduler.auto_schedule_networks( + self.plugin, self.ctx, HOST_C) + self.assertTrue(observed_val_second_segment) + agents = self.plugin.get_dhcp_agents_hosting_networks( + self.ctx, [net_id]) + self.assertEqual(2, len(agents)) + + class TestNetworksFailover(TestDhcpSchedulerBaseTestCase, sched_db.DhcpAgentSchedulerDbMixin, common_db_mixin.CommonDbMixin):