Merge "DHCP Agent scheduling with segments"
This commit is contained in:
commit
f545a4e9b0
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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': [],
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
Loading…
Reference in New Issue