Merge "DHCP Agent scheduling with segments"

This commit is contained in:
Jenkins 2016-06-27 21:54:00 +00:00 committed by Gerrit Code Review
commit f545a4e9b0
7 changed files with 243 additions and 13 deletions

View File

@ -149,9 +149,17 @@ class DhcpAgentNotifyAPI(object):
elif cast_required:
admin_ctx = (context if context.is_admin else context.elevated())
network = self.plugin.get_network(admin_ctx, network_id)
agents = self.plugin.get_dhcp_agents_hosting_networks(
context, [network_id])
if 'subnet' in payload and payload['subnet'].get('segment_id'):
# if segment_id exists then the segment service plugin
# must be loaded
nm = manager.NeutronManager
segment_plugin = nm.get_service_plugins()['segments']
segment = segment_plugin.get_segment(
context, payload['subnet']['segment_id'])
network['candidate_hosts'] = segment['hosts']
agents = self.plugin.get_dhcp_agents_hosting_networks(
context, [network_id], hosts=network.get('candidate_hosts'))
# schedule the network first, if needed
schedule_required = (
method == 'subnet_create_end' or

View File

@ -438,7 +438,8 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
"rescheduling"))
def get_dhcp_agents_hosting_networks(
self, context, network_ids, active=None, admin_state_up=None):
self, context, network_ids, active=None, admin_state_up=None,
hosts=None):
if not network_ids:
return []
query = context.session.query(ndab_model.NetworkDhcpAgentBinding)
@ -448,6 +449,8 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
if network_ids:
query = query.filter(
ndab_model.NetworkDhcpAgentBinding.network_id.in_(network_ids))
if hosts:
query = query.filter(agents_db.Agent.host.in_(hosts))
if admin_state_up is not None:
query = query.filter(agents_db.Agent.admin_state_up ==
admin_state_up)

View File

@ -199,6 +199,18 @@ class DhcpFilter(base_resource_filter.BaseResourceFilter):
'hosted_agents': agents_dict['hosted_agents']}
return agents_dict
def _filter_agents_with_network_access(self, hostable_agents, plugin,
context, network):
if 'candidate_hosts' in network:
hostable_dhcp_hosts = network['candidate_hosts']
else:
hostable_dhcp_hosts = plugin.filter_hosts_with_network_access(
context, network['id'],
[agent['host'] for agent in hostable_agents])
reachable_agents = [agent for agent in hostable_agents
if agent['host'] in hostable_dhcp_hosts]
return reachable_agents
def _get_dhcp_agents_hosting_network(self, plugin, context, network):
"""Return dhcp agents hosting the given network or None if a given
network is already hosted by enough number of agents.
@ -208,7 +220,7 @@ class DhcpFilter(base_resource_filter.BaseResourceFilter):
# subnets whose enable_dhcp is false
with context.session.begin(subtransactions=True):
network_hosted_agents = plugin.get_dhcp_agents_hosting_networks(
context, [network['id']])
context, [network['id']], hosts=network.get('candidate_hosts'))
if len(network_hosted_agents) >= agents_per_network:
LOG.debug('Network %s is already hosted by enough agents.',
network['id'])
@ -253,11 +265,8 @@ class DhcpFilter(base_resource_filter.BaseResourceFilter):
context, True, agent)
]
hostable_dhcp_hosts = plugin.filter_hosts_with_network_access(
context, network['id'],
[agent['host'] for agent in hostable_dhcp_agents])
hostable_dhcp_agents = [agent for agent in hostable_dhcp_agents
if agent['host'] in hostable_dhcp_hosts]
hostable_dhcp_agents = self._filter_agents_with_network_access(
hostable_dhcp_agents, plugin, context, network)
if not hostable_dhcp_agents:
return {'n_agents': 0, 'hostable_agents': [],

View File

@ -77,7 +77,9 @@ class SegmentDbMixin(common_db_mixin.CommonDbMixin):
'network_id': segment_db['network_id'],
db.PHYSICAL_NETWORK: segment_db[db.PHYSICAL_NETWORK],
db.NETWORK_TYPE: segment_db[db.NETWORK_TYPE],
db.SEGMENTATION_ID: segment_db[db.SEGMENTATION_ID]}
db.SEGMENTATION_ID: segment_db[db.SEGMENTATION_ID],
'hosts': [mapping.host for mapping in
segment_db.segment_host_mapping]}
return self._fields(res, fields)
def _get_segment(self, context, segment_id):

View File

@ -151,10 +151,12 @@ class TestDhcpAgentNotifyAPI(base.BaseTestCase):
self.assertEqual(expected_casts, self.mock_cast.call_count)
def _test__notify_agents(self, method,
expected_scheduling=0, expected_casts=0):
expected_scheduling=0, expected_casts=0,
payload=None):
payload = payload or {'port': {}}
self._test__notify_agents_with_function(
lambda: self.notifier._notify_agents(
mock.Mock(), method, {'port': {}}, 'foo_network_id'),
mock.Mock(), method, payload, 'foo_network_id'),
expected_scheduling, expected_casts)
def test__notify_agents_cast_required_with_scheduling(self):
@ -167,7 +169,26 @@ class TestDhcpAgentNotifyAPI(base.BaseTestCase):
def test__notify_agents_cast_required_with_scheduling_subnet_create(self):
self._test__notify_agents('subnet_create_end',
expected_scheduling=1, expected_casts=1)
expected_scheduling=1, expected_casts=1,
payload={'subnet': {}})
def test__notify_agents_cast_required_with_scheduling_segment(self):
network_id = 'foo_network_id'
segment_id = 'foo_segment_id'
subnet = {'subnet': {'segment_id': segment_id}}
segment = {'id': segment_id, 'network_id': network_id,
'hosts': ['host-a']}
self.notifier.plugin.get_network.return_value = {'id': network_id}
segment_sp = mock.Mock()
segment_sp.get_segment.return_value = segment
with mock.patch('neutron.manager.NeutronManager.get_service_plugins',
return_value={'segments': segment_sp}):
self._test__notify_agents('subnet_create_end',
expected_scheduling=1, expected_casts=1,
payload=subnet)
get_agents = self.notifier.plugin.get_dhcp_agents_hosting_networks
get_agents.assert_called_once_with(
mock.ANY, [network_id], hosts=segment['hosts'])
def test__notify_agents_no_action(self):
self._test__notify_agents('network_create_end',

View File

@ -21,6 +21,7 @@ import webob.exc
from neutron.api.v2 import attributes
from neutron import context
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
@ -36,6 +37,8 @@ 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'
class SegmentTestExtensionManager(object):
@ -894,3 +897,92 @@ class TestSegmentAwareIpamML2(TestSegmentAwareIpam):
def setUp(self):
super(TestSegmentAwareIpamML2, self).setUp(
plugin='neutron.plugins.ml2.plugin.Ml2Plugin')
class TestDhcpAgentSegmentScheduling(HostSegmentMappingTestCase):
_mechanism_drivers = ['openvswitch', 'logger']
mock_path = 'neutron.services.segments.db.update_segment_host_mapping'
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='vxlan')
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 = 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='vxlan')['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)

View File

@ -28,6 +28,7 @@ from neutron.db import models_v2
from neutron.db.network_dhcp_agent_binding import models as ndab_model
from neutron.extensions import dhcpagentscheduler
from neutron.scheduler import dhcp_agent_scheduler
from neutron.services.segments import db as segments_service_db
from neutron.tests.common import helpers
from neutron.tests.unit.plugins.ml2 import test_plugin
from neutron.tests.unit import testlib_api
@ -405,6 +406,8 @@ class DHCPAgentWeightSchedulerTestCase(test_plugin.Ml2PluginV2TestCase):
self.plugin.network_scheduler = importutils.import_object(
weight_scheduler)
cfg.CONF.set_override("dhcp_load_type", "networks")
self.segments_plugin = importutils.import_object(
'neutron.services.segments.plugin.Plugin')
self.ctx = context.get_admin_context()
def _create_network(self):
@ -416,6 +419,15 @@ class DHCPAgentWeightSchedulerTestCase(test_plugin.Ml2PluginV2TestCase):
'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 test_scheduler_one_agents_per_network(self):
net_id = self._create_network()
helpers.register_dhcp_agent(HOST_C)
@ -468,6 +480,85 @@ class DHCPAgentWeightSchedulerTestCase(test_plugin.Ml2PluginV2TestCase):
self.assertEqual('host-c', agent2[0]['host'])
self.assertEqual('host-d', agent3[0]['host'])
def test_schedule_segment_one_hostable_agent(self):
net_id = self._create_network()
seg_id = self._create_segment(net_id)
helpers.register_dhcp_agent(HOST_C)
helpers.register_dhcp_agent(HOST_D)
segments_service_db.update_segment_host_mapping(
self.ctx, HOST_C, {seg_id})
net = self.plugin.get_network(self.ctx, net_id)
seg = self.segments_plugin.get_segment(self.ctx, seg_id)
net['candidate_hosts'] = seg['hosts']
agents = self.plugin.network_scheduler.schedule(
self.plugin, self.ctx, net)
self.assertEqual(1, len(agents))
self.assertEqual(HOST_C, agents[0].host)
def test_schedule_segment_many_hostable_agents(self):
net_id = self._create_network()
seg_id = self._create_segment(net_id)
helpers.register_dhcp_agent(HOST_C)
helpers.register_dhcp_agent(HOST_D)
segments_service_db.update_segment_host_mapping(
self.ctx, HOST_C, {seg_id})
segments_service_db.update_segment_host_mapping(
self.ctx, HOST_D, {seg_id})
net = self.plugin.get_network(self.ctx, net_id)
seg = self.segments_plugin.get_segment(self.ctx, seg_id)
net['candidate_hosts'] = seg['hosts']
agents = self.plugin.network_scheduler.schedule(
self.plugin, self.ctx, net)
self.assertEqual(1, len(agents))
self.assertIn(agents[0].host, [HOST_C, HOST_D])
def test_schedule_segment_no_host_mapping(self):
net_id = self._create_network()
seg_id = self._create_segment(net_id)
helpers.register_dhcp_agent(HOST_C)
helpers.register_dhcp_agent(HOST_D)
net = self.plugin.get_network(self.ctx, net_id)
seg = self.segments_plugin.get_segment(self.ctx, seg_id)
net['candidate_hosts'] = seg['hosts']
agents = self.plugin.network_scheduler.schedule(
self.plugin, self.ctx, net)
self.assertEqual(0, len(agents))
def test_schedule_segment_two_agents_per_segment(self):
cfg.CONF.set_override('dhcp_agents_per_network', 2)
net_id = self._create_network()
seg_id = self._create_segment(net_id)
helpers.register_dhcp_agent(HOST_C)
helpers.register_dhcp_agent(HOST_D)
segments_service_db.update_segment_host_mapping(
self.ctx, HOST_C, {seg_id})
segments_service_db.update_segment_host_mapping(
self.ctx, HOST_D, {seg_id})
net = self.plugin.get_network(self.ctx, net_id)
seg = self.segments_plugin.get_segment(self.ctx, seg_id)
net['candidate_hosts'] = seg['hosts']
agents = self.plugin.network_scheduler.schedule(
self.plugin, self.ctx, net)
self.assertEqual(2, len(agents))
self.assertIn(agents[0].host, [HOST_C, HOST_D])
self.assertIn(agents[1].host, [HOST_C, HOST_D])
def test_schedule_segment_two_agents_per_segment_one_hostable_agent(self):
cfg.CONF.set_override('dhcp_agents_per_network', 2)
net_id = self._create_network()
seg_id = self._create_segment(net_id)
helpers.register_dhcp_agent(HOST_C)
helpers.register_dhcp_agent(HOST_D)
segments_service_db.update_segment_host_mapping(
self.ctx, HOST_C, {seg_id})
net = self.plugin.get_network(self.ctx, net_id)
seg = self.segments_plugin.get_segment(self.ctx, seg_id)
net['candidate_hosts'] = seg['hosts']
agents = self.plugin.network_scheduler.schedule(
self.plugin, self.ctx, net)
self.assertEqual(1, len(agents))
self.assertEqual(HOST_C, agents[0].host)
class TestDhcpSchedulerFilter(TestDhcpSchedulerBaseTestCase,
sched_db.DhcpAgentSchedulerDbMixin):
@ -518,6 +609,10 @@ class TestDhcpSchedulerFilter(TestDhcpSchedulerBaseTestCase,
'host-c', 'host-d'},
networks=networks)
def test_get_dhcp_agents_host_network_filter_by_hosts(self):
self._test_get_dhcp_agents_hosting_networks({'host-a'},
hosts=['host-a'])
class DHCPAgentAZAwareWeightSchedulerTestCase(TestDhcpSchedulerBaseTestCase):