OVO for NetworkDhcpAgentBinding

This patch introduces and implements Oslo-Versioned Objects for
NetworkDhcpAgentBinding

Partially-Implements: blueprint adopt-oslo-versioned-objects-for-db
Co-Authored-By: Manjeet Singh Bhatia <manjeet.s.bhatia@intel.com>

Change-Id: Ie6220f8a1455ea721f0d9c7a1b58240cc5fde05a
This commit is contained in:
Nakul Dahiwade 2016-11-10 11:35:04 +01:00 committed by Manjeet Singh Bhatia
parent 96f8fbc62d
commit 7c7b2d75aa
9 changed files with 227 additions and 133 deletions

View File

@ -23,7 +23,6 @@ from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
import oslo_messaging import oslo_messaging
from oslo_utils import timeutils from oslo_utils import timeutils
from sqlalchemy import orm
from sqlalchemy.orm import exc from sqlalchemy.orm import exc
from neutron._i18n import _ from neutron._i18n import _
@ -32,10 +31,9 @@ from neutron.common import constants as n_const
from neutron.common import utils from neutron.common import utils
from neutron.db import agents_db from neutron.db import agents_db
from neutron.db.availability_zone import network as network_az from neutron.db.availability_zone import network as network_az
from neutron.db.models import agent as agent_model
from neutron.db.network_dhcp_agent_binding import models as ndab_model
from neutron.extensions import agent as ext_agent from neutron.extensions import agent as ext_agent
from neutron.extensions import dhcpagentscheduler from neutron.extensions import dhcpagentscheduler
from neutron.objects import network
from neutron import worker as neutron_worker from neutron import worker as neutron_worker
@ -234,9 +232,8 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
""" """
agent_dead_limit = datetime.timedelta( agent_dead_limit = datetime.timedelta(
seconds=self.agent_dead_limit_seconds()) seconds=self.agent_dead_limit_seconds())
network_count = (context.session.query(ndab_model. network_count = network.NetworkDhcpAgentBinding.count(
NetworkDhcpAgentBinding). context, dhcp_agent_id=agent['id'])
filter_by(dhcp_agent_id=agent['id']).count())
# amount of networks assigned to agent affect amount of time we give # amount of networks assigned to agent affect amount of time we give
# it so startup. Tests show that it's more or less sage to assume # it so startup. Tests show that it's more or less sage to assume
# that DHCP agent processes each network in less than 2 seconds. # that DHCP agent processes each network in less than 2 seconds.
@ -285,9 +282,10 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
checked_agents = {} checked_agents = {}
for binding in bindings: for binding in bindings:
try: try:
agent_id = binding.dhcp_agent['id'] agent_id = binding.db_obj.dhcp_agent['id']
if agent_id not in checked_agents: if agent_id not in checked_agents:
if self.agent_starting_up(context, binding.dhcp_agent): if self.agent_starting_up(context,
binding.db_obj.dhcp_agent):
# When agent starts and it has many networks to process # When agent starts and it has many networks to process
# it may fail to send state reports in defined interval # it may fail to send state reports in defined interval
# The server will consider it dead and try to remove # The server will consider it dead and try to remove
@ -316,11 +314,8 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
context = ncontext.get_admin_context() context = ncontext.get_admin_context()
try: try:
down_bindings = ( down_bindings = network.NetworkDhcpAgentBinding.get_down_bindings(
context.session.query(ndab_model.NetworkDhcpAgentBinding). context, cutoff)
join(agent_model.Agent).
filter(agent_model.Agent.heartbeat_timestamp < cutoff,
agent_model.Agent.admin_state_up))
dhcp_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_DHCP) dhcp_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_DHCP)
dead_bindings = [b for b in dead_bindings = [b for b in
self._filter_bindings(context, down_bindings)] self._filter_bindings(context, down_bindings)]
@ -381,23 +376,23 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
hosts=None): hosts=None):
if not network_ids: if not network_ids:
return [] return []
query = context.session.query(ndab_model.NetworkDhcpAgentBinding) # get all the NDAB objects, which will also fetch (from DB)
query = query.options(orm.contains_eager( # the related dhcp_agent objects because of the synthetic field
ndab_model.NetworkDhcpAgentBinding.dhcp_agent)) bindings = network.NetworkDhcpAgentBinding.get_objects(
query = query.join(ndab_model.NetworkDhcpAgentBinding.dhcp_agent) context, network_id=network_ids)
if network_ids: # get the already fetched dhcp_agent objects
query = query.filter( agent_objs = [binding.db_obj.dhcp_agent for binding in bindings]
ndab_model.NetworkDhcpAgentBinding.network_id.in_(network_ids)) # filter the dhcp_agent objects on admin_state_up
if hosts:
query = query.filter(agent_model.Agent.host.in_(hosts))
if admin_state_up is not None: if admin_state_up is not None:
query = query.filter(agent_model.Agent.admin_state_up == agent_objs = [agent for agent in agent_objs
admin_state_up) if agent.admin_state_up == admin_state_up]
# filter the dhcp_agent objects on hosts
return [binding.dhcp_agent if hosts:
for binding in query agent_objs = [agent for agent in agent_objs
if self.is_eligible_agent(context, active, if agent.host in hosts]
binding.dhcp_agent)] # finally filter if the agents are eligible
return [agent for agent in agent_objs
if self.is_eligible_agent(context, active, agent)]
def add_network_to_dhcp_agent(self, context, id, network_id): def add_network_to_dhcp_agent(self, context, id, network_id):
self._get_network(context, network_id) self._get_network(context, network_id)
@ -412,10 +407,8 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
if id == dhcp_agent.id: if id == dhcp_agent.id:
raise dhcpagentscheduler.NetworkHostedByDHCPAgent( raise dhcpagentscheduler.NetworkHostedByDHCPAgent(
network_id=network_id, agent_id=id) network_id=network_id, agent_id=id)
binding = ndab_model.NetworkDhcpAgentBinding() network.NetworkDhcpAgentBinding(context, dhcp_agent_id=id,
binding.dhcp_agent_id = id network_id=network_id).create()
binding.network_id = network_id
context.session.add(binding)
dhcp_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_DHCP) dhcp_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_DHCP)
if dhcp_notifier: if dhcp_notifier:
dhcp_notifier.network_added_to_agent( dhcp_notifier.network_added_to_agent(
@ -424,12 +417,9 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
def remove_network_from_dhcp_agent(self, context, id, network_id, def remove_network_from_dhcp_agent(self, context, id, network_id,
notify=True): notify=True):
agent = self._get_agent(context, id) agent = self._get_agent(context, id)
try: binding_obj = network.NetworkDhcpAgentBinding.get_object(
query = context.session.query(ndab_model.NetworkDhcpAgentBinding) context, network_id=network_id, dhcp_agent_id=id)
binding = query.filter( if not binding_obj:
ndab_model.NetworkDhcpAgentBinding.network_id == network_id,
ndab_model.NetworkDhcpAgentBinding.dhcp_agent_id == id).one()
except exc.NoResultFound:
raise dhcpagentscheduler.NetworkNotHostedByDhcpAgent( raise dhcpagentscheduler.NetworkNotHostedByDhcpAgent(
network_id=network_id, agent_id=id) network_id=network_id, agent_id=id)
@ -444,8 +434,7 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
for port in ports: for port in ports:
port['device_id'] = n_const.DEVICE_ID_RESERVED_DHCP_PORT port['device_id'] = n_const.DEVICE_ID_RESERVED_DHCP_PORT
self.update_port(context, port['id'], dict(port=port)) self.update_port(context, port['id'], dict(port=port))
with context.session.begin(): binding_obj.delete()
context.session.delete(binding)
if not notify: if not notify:
return return
@ -455,12 +444,9 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
context, network_id, agent.host) context, network_id, agent.host)
def list_networks_on_dhcp_agent(self, context, id): def list_networks_on_dhcp_agent(self, context, id):
query = context.session.query( objs = network.NetworkDhcpAgentBinding.get_objects(context,
ndab_model.NetworkDhcpAgentBinding.network_id) dhcp_agent_id=id)
query = query.filter( net_ids = [item.network_id for item in objs]
ndab_model.NetworkDhcpAgentBinding.dhcp_agent_id == id)
net_ids = [item[0] for item in query]
if net_ids: if net_ids:
return {'networks': return {'networks':
self.get_networks(context, filters={'id': net_ids})} self.get_networks(context, filters={'id': net_ids})}
@ -479,12 +465,11 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
if not services_available(agent.admin_state_up): if not services_available(agent.admin_state_up):
return [] return []
query = context.session.query(
ndab_model.NetworkDhcpAgentBinding.network_id)
query = query.filter(
ndab_model.NetworkDhcpAgentBinding.dhcp_agent_id == agent.id)
net_ids = [item[0] for item in query] query = network.NetworkDhcpAgentBinding.get_objects(
context, dhcp_agent_id=agent.id)
net_ids = [item.network_id for item in query]
if net_ids: if net_ids:
return self.get_networks( return self.get_networks(
context, context,

View File

@ -23,7 +23,7 @@ class NetworkDhcpAgentBinding(model_base.BASEV2):
network_id = sa.Column(sa.String(36), network_id = sa.Column(sa.String(36),
sa.ForeignKey("networks.id", ondelete='CASCADE'), sa.ForeignKey("networks.id", ondelete='CASCADE'),
primary_key=True) primary_key=True)
dhcp_agent = orm.relation(agent_model.Agent) dhcp_agent = orm.relation(agent_model.Agent, lazy='subquery')
dhcp_agent_id = sa.Column(sa.String(36), dhcp_agent_id = sa.Column(sa.String(36),
sa.ForeignKey("agents.id", sa.ForeignKey("agents.id",
ondelete='CASCADE'), ondelete='CASCADE'),

View File

@ -20,9 +20,11 @@ from neutron.db.models import dns as dns_models
from neutron.db.models import external_net as ext_net_model from neutron.db.models import external_net as ext_net_model
from neutron.db.models import segment as segment_model from neutron.db.models import segment as segment_model
from neutron.db import models_v2 from neutron.db import models_v2
from neutron.db.network_dhcp_agent_binding import models as ndab_models
from neutron.db.port_security import models as ps_models from neutron.db.port_security import models as ps_models
from neutron.db import rbac_db_models from neutron.db import rbac_db_models
from neutron.extensions import availability_zone as az_ext from neutron.extensions import availability_zone as az_ext
from neutron.objects import agent as agent_obj
from neutron.objects import base from neutron.objects import base
from neutron.objects import common_types from neutron.objects import common_types
from neutron.objects.extensions import port_security as base_ps from neutron.objects.extensions import port_security as base_ps
@ -30,6 +32,30 @@ from neutron.objects.qos import binding
from neutron.objects import rbac_db from neutron.objects import rbac_db
@obj_base.VersionedObjectRegistry.register
class NetworkDhcpAgentBinding(base.NeutronDbObject):
# Version 1.0: Initial version
VERSION = '1.0'
db_model = ndab_models.NetworkDhcpAgentBinding
primary_keys = ['network_id', 'dhcp_agent_id']
fields = {
'network_id': common_types.UUIDField(),
'dhcp_agent_id': common_types.UUIDField(),
}
# NOTE(ndahiwade): The join was implemented this way as get_objects
# currently doesn't support operators like '<' or '>'
@classmethod
def get_down_bindings(cls, context, cutoff):
agent_objs = agent_obj.Agent.get_objects(context)
dhcp_agent_ids = [obj.id for obj in agent_objs
if obj.heartbeat_timestamp < cutoff]
return cls.get_objects(context, dhcp_agent_id=dhcp_agent_ids)
@obj_base.VersionedObjectRegistry.register @obj_base.VersionedObjectRegistry.register
class NetworkSegment(base.NeutronDbObject): class NetworkSegment(base.NeutronDbObject):
# Version 1.0: Initial version # Version 1.0: Initial version

View File

@ -18,15 +18,13 @@ import collections
from operator import itemgetter from operator import itemgetter
from neutron_lib import constants from neutron_lib import constants
from neutron_lib.objects import exceptions
from oslo_config import cfg from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log as logging from oslo_log import log as logging
from sqlalchemy import sql from sqlalchemy import sql
from neutron.agent.common import utils as agent_utils from neutron.agent.common import utils as agent_utils
from neutron.db import api as db_api
from neutron.db.models import agent as agent_model from neutron.db.models import agent as agent_model
from neutron.db.network_dhcp_agent_binding import models as ndab_model
from neutron.extensions import availability_zone as az_ext from neutron.extensions import availability_zone as az_ext
from neutron.objects import network from neutron.objects import network
from neutron.scheduler import base_resource_filter from neutron.scheduler import base_resource_filter
@ -181,15 +179,10 @@ class DhcpFilter(base_resource_filter.BaseResourceFilter):
# saving agent_id to use it after rollback to avoid # saving agent_id to use it after rollback to avoid
# DetachedInstanceError # DetachedInstanceError
agent_id = agent.id agent_id = agent.id
binding = ndab_model.NetworkDhcpAgentBinding()
binding.dhcp_agent_id = agent_id
binding.network_id = network_id
try: try:
with db_api.autonested_transaction(context.session): network.NetworkDhcpAgentBinding(context,
context.session.add(binding) dhcp_agent_id=agent_id, network_id=network_id).create()
# try to actually write the changes and catch integrity except exceptions.NeutronDbObjectDuplicateEntry:
# DBDuplicateEntry
except db_exc.DBDuplicateEntry:
# it's totally ok, someone just did our job! # it's totally ok, someone just did our job!
bound_agents.remove(agent) bound_agents.remove(agent)
LOG.info('Agent %s already present', agent_id) LOG.info('Agent %s already present', agent_id)
@ -273,15 +266,14 @@ class DhcpFilter(base_resource_filter.BaseResourceFilter):
az_hints = (network.get(az_ext.AZ_HINTS) or az_hints = (network.get(az_ext.AZ_HINTS) or
cfg.CONF.default_availability_zones) cfg.CONF.default_availability_zones)
active_dhcp_agents = self._get_active_agents(plugin, context, az_hints) active_dhcp_agents = self._get_active_agents(plugin, context, az_hints)
hosted_agent_ids = [agent['id'] for agent in hosted_agents]
if not active_dhcp_agents: if not active_dhcp_agents:
return {'n_agents': 0, 'hostable_agents': [], return {'n_agents': 0, 'hostable_agents': [],
'hosted_agents': hosted_agents} 'hosted_agents': hosted_agents}
hostable_dhcp_agents = [ hostable_dhcp_agents = [
agent for agent in set(active_dhcp_agents) agent for agent in active_dhcp_agents
if agent not in hosted_agents and plugin.is_eligible_agent( if agent.id not in hosted_agent_ids and plugin.is_eligible_agent(
context, True, agent) context, True, agent)]
]
hostable_dhcp_agents = self._filter_agents_with_network_access( hostable_dhcp_agents = self._filter_agents_with_network_access(
plugin, context, network, hostable_dhcp_agents) plugin, context, network, hostable_dhcp_agents)

View File

@ -19,12 +19,13 @@ from operator import attrgetter
from neutron_lib.api.definitions import provider_net as providernet from neutron_lib.api.definitions import provider_net as providernet
from neutron_lib import constants from neutron_lib import constants
from neutron_lib import context from neutron_lib import context
from oslo_utils import uuidutils
import testscenarios import testscenarios
from neutron.db import agents_db from neutron.db import agents_db
from neutron.db import agentschedulers_db from neutron.db import agentschedulers_db
from neutron.db import common_db_mixin from neutron.db import common_db_mixin
from neutron.db.network_dhcp_agent_binding import models as ndab_model from neutron.objects import network
from neutron.scheduler import dhcp_agent_scheduler from neutron.scheduler import dhcp_agent_scheduler
from neutron.tests.common import helpers from neutron.tests.common import helpers
from neutron.tests.unit.plugins.ml2 import test_plugin from neutron.tests.unit.plugins.ml2 import test_plugin
@ -359,10 +360,10 @@ class TestAutoSchedule(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
def get_subnets(self, context, fields=None): def get_subnets(self, context, fields=None):
subnets = [] subnets = []
for net_id in self._networks: for net in self._networks:
enable_dhcp = (not self._strip_host_index(net_id) in enable_dhcp = (self._strip_host_index(net['name']) not in
self.networks_with_dhcp_disabled) self.networks_with_dhcp_disabled)
subnets.append({'network_id': net_id, subnets.append({'network_id': net.id,
'enable_dhcp': enable_dhcp, 'enable_dhcp': enable_dhcp,
'segment_id': None}) 'segment_id': None})
return subnets return subnets
@ -374,13 +375,9 @@ class TestAutoSchedule(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
return {'availability_zone_hints': az_hints} return {'availability_zone_hints': az_hints}
def _get_hosted_networks_on_dhcp_agent(self, agent_id): def _get_hosted_networks_on_dhcp_agent(self, agent_id):
query = self.ctx.session.query( binding_objs = network.NetworkDhcpAgentBinding.get_objects(
ndab_model.NetworkDhcpAgentBinding.network_id) self.ctx, dhcp_agent_id=agent_id)
query = query.filter( return [item.network_id for item in binding_objs]
ndab_model.NetworkDhcpAgentBinding.dhcp_agent_id ==
agent_id)
return [item[0] for item in query]
def _test_auto_schedule(self, host_index): def _test_auto_schedule(self, host_index):
self.config(dhcp_agents_per_network=self.max_agents_per_network) self.config(dhcp_agents_per_network=self.max_agents_per_network)
@ -394,9 +391,16 @@ class TestAutoSchedule(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
dhcp_agents = self._create_and_set_agents_down(hosts) dhcp_agents = self._create_and_set_agents_down(hosts)
# create networks # create networks
self._networks = ['%s-network-%s' % (host_index, i) self._networks = [
for i in range(self.network_count)] network.Network(
self._save_networks(self._networks) self.ctx,
id=uuidutils.generate_uuid(),
name='%s-network-%s' % (host_index, i))
for i in range(self.network_count)
]
for i in range(len(self._networks)):
self._networks[i].create()
network_ids = [net.id for net in self._networks]
# pre schedule the networks to the agents defined in # pre schedule the networks to the agents defined in
# self.hosted_networks before calling auto_schedule_network # self.hosted_networks before calling auto_schedule_network
@ -406,7 +410,7 @@ class TestAutoSchedule(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
net_index = self._extract_index(net) net_index = self._extract_index(net)
scheduler.resource_filter.bind(self.ctx, scheduler.resource_filter.bind(self.ctx,
[dhcp_agents[agent_index]], [dhcp_agents[agent_index]],
self._networks[net_index]) network_ids[net_index])
retval = scheduler.auto_schedule_networks(self, self.ctx, retval = scheduler.auto_schedule_networks(self, self.ctx,
hosts[host_index]) hosts[host_index])
@ -415,11 +419,14 @@ class TestAutoSchedule(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
agent_id = dhcp_agents[host_index].id agent_id = dhcp_agents[host_index].id
hosted_networks = self._get_hosted_networks_on_dhcp_agent(agent_id) hosted_networks = self._get_hosted_networks_on_dhcp_agent(agent_id)
hosted_net_ids = [self._strip_host_index(net) hosted_net_names = [
for net in hosted_networks] self._strip_host_index(net['name'])
for net in network.Network.get_objects(
self.ctx, id=hosted_networks)
]
expected_hosted_networks = self.expected_hosted_networks['agent-%s' % expected_hosted_networks = self.expected_hosted_networks['agent-%s' %
host_index] host_index]
self.assertItemsEqual(hosted_net_ids, expected_hosted_networks, msg) self.assertItemsEqual(hosted_net_names, expected_hosted_networks, msg)
def test_auto_schedule(self): def test_auto_schedule(self):
for i in range(self.agent_count): for i in range(self.agent_count):

View File

@ -1715,7 +1715,17 @@ class BaseDbObjectTestCase(_BaseObjectTestCase,
# check that the stored database model does not have non-empty # check that the stored database model does not have non-empty
# relationships # relationships
dbattr = obj.fields_need_translation.get(field, field) dbattr = obj.fields_need_translation.get(field, field)
self.assertFalse(getattr(obj.db_obj, dbattr, None)) # Skipping empty relationships for the following reasons:
# 1) db_obj have the related object loaded - In this case we do not
# have to create the related objects and the loop can continue.
# 2) when the related objects are not loaded - In this
# case they need to be created because of the foreign key
# relationships. But we still need to check whether the
# relationships are loaded or not. That is achieved by the
# assertTrue statement after retrieving the dbattr in
# this method.
if getattr(obj.db_obj, dbattr, None):
continue
if isinstance(cls_.fields[field], obj_fields.ObjectField): if isinstance(cls_.fields[field], obj_fields.ObjectField):
objclass_fields = self._get_non_synth_fields(objclass, objclass_fields = self._get_non_synth_fields(objclass,

View File

@ -20,6 +20,26 @@ from neutron.tests.unit.objects import test_base as obj_test_base
from neutron.tests.unit import testlib_api from neutron.tests.unit import testlib_api
class NetworkDhcpAgentBindingObjectIfaceTestCase(
obj_test_base.BaseObjectIfaceTestCase):
_test_class = network.NetworkDhcpAgentBinding
class NetworkDhcpAgentBindingDbObjectTestCase(
obj_test_base.BaseDbObjectTestCase, testlib_api.SqlTestCase):
_test_class = network.NetworkDhcpAgentBinding
def setUp(self):
super(NetworkDhcpAgentBindingDbObjectTestCase, self).setUp()
self._network = self._create_test_network()
self.update_obj_fields(
{'network_id': self._network.id,
'dhcp_agent_id': lambda: self._create_test_agent_id()})
class NetworkPortSecurityIfaceObjTestCase( class NetworkPortSecurityIfaceObjTestCase(
obj_test_base.BaseObjectIfaceTestCase): obj_test_base.BaseObjectIfaceTestCase):
_test_class = network.NetworkPortSecurity _test_class = network.NetworkPortSecurity

View File

@ -54,6 +54,7 @@ object_data = {
'MeteringLabelRule': '1.0-b5c5717e7bab8d1af1623156012a5842', 'MeteringLabelRule': '1.0-b5c5717e7bab8d1af1623156012a5842',
'Log': '1.0-6391351c0f34ed34375a19202f361d24', 'Log': '1.0-6391351c0f34ed34375a19202f361d24',
'Network': '1.0-f2f6308f79731a767b92b26b0f4f3849', 'Network': '1.0-f2f6308f79731a767b92b26b0f4f3849',
'NetworkDhcpAgentBinding': '1.0-6eeceb5fb4335cd65a305016deb41c68',
'NetworkDNSDomain': '1.0-420db7910294608534c1e2e30d6d8319', 'NetworkDNSDomain': '1.0-420db7910294608534c1e2e30d6d8319',
'NetworkPortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3', 'NetworkPortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3',
'NetworkSegment': '1.0-57b7f2960971e3b95ded20cbc59244a8', 'NetworkSegment': '1.0-57b7f2960971e3b95ded20cbc59244a8',

View File

@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import datetime
import random import random
import mock import mock
@ -20,13 +21,15 @@ from neutron_lib import constants
from neutron_lib import context from neutron_lib import context
from oslo_config import cfg from oslo_config import cfg
from oslo_utils import importutils from oslo_utils import importutils
from oslo_utils import uuidutils
import testscenarios import testscenarios
from neutron.db import agentschedulers_db as sched_db from neutron.db import agentschedulers_db as sched_db
from neutron.db import common_db_mixin from neutron.db import common_db_mixin
from neutron.db import models_v2 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.extensions import dhcpagentscheduler
from neutron.objects import agent
from neutron.objects import network as network_obj
from neutron.scheduler import dhcp_agent_scheduler from neutron.scheduler import dhcp_agent_scheduler
from neutron.services.segments import db as segments_service_db from neutron.services.segments import db as segments_service_db
from neutron.tests.common import helpers from neutron.tests.common import helpers
@ -47,8 +50,8 @@ class TestDhcpSchedulerBaseTestCase(testlib_api.SqlTestCase):
super(TestDhcpSchedulerBaseTestCase, self).setUp() super(TestDhcpSchedulerBaseTestCase, self).setUp()
self.setup_coreplugin(self.CORE_PLUGIN) self.setup_coreplugin(self.CORE_PLUGIN)
self.ctx = context.get_admin_context() self.ctx = context.get_admin_context()
self.network = {'id': 'foo_network_id'} self.network = {'id': uuidutils.generate_uuid()}
self.network_id = 'foo_network_id' self.network_id = self.network['id']
self._save_networks([self.network_id]) self._save_networks([self.network_id])
def _create_and_set_agents_down(self, hosts, down_agent_count=0, def _create_and_set_agents_down(self, hosts, down_agent_count=0,
@ -72,11 +75,10 @@ class TestDhcpSchedulerBaseTestCase(testlib_api.SqlTestCase):
def _test_schedule_bind_network(self, agents, network_id): def _test_schedule_bind_network(self, agents, network_id):
scheduler = dhcp_agent_scheduler.ChanceScheduler() scheduler = dhcp_agent_scheduler.ChanceScheduler()
scheduler.resource_filter.bind(self.ctx, agents, network_id) scheduler.resource_filter.bind(self.ctx, agents, network_id)
results = self.ctx.session.query( binding_objs = network_obj.NetworkDhcpAgentBinding.get_objects(
ndab_model.NetworkDhcpAgentBinding).filter_by( self.ctx, network_id=network_id)
network_id=network_id).all() self.assertEqual(len(agents), len(binding_objs))
self.assertEqual(len(agents), len(results)) for result in binding_objs:
for result in results:
self.assertEqual(network_id, result.network_id) self.assertEqual(network_id, result.network_id)
@ -135,9 +137,8 @@ class TestDhcpScheduler(TestDhcpSchedulerBaseTestCase):
self._test_reschedule_vs_network_on_dead_agent(False) self._test_reschedule_vs_network_on_dead_agent(False)
def _get_agent_binding_from_db(self, agent): def _get_agent_binding_from_db(self, agent):
return self.ctx.session.query( return network_obj.NetworkDhcpAgentBinding.get_objects(
ndab_model.NetworkDhcpAgentBinding self.ctx, dhcp_agent_id=agent[0].id)
).filter_by(dhcp_agent_id=agent[0].id).all()
def _test_auto_reschedule_vs_network_on_dead_agent(self, def _test_auto_reschedule_vs_network_on_dead_agent(self,
active_hosts_only): active_hosts_only):
@ -289,9 +290,9 @@ class TestAutoScheduleNetworks(TestDhcpSchedulerBaseTestCase):
observed_ret_value = scheduler.auto_schedule_networks( observed_ret_value = scheduler.auto_schedule_networks(
plugin, self.ctx, host) plugin, self.ctx, host)
self.assertEqual(expected_result, observed_ret_value) self.assertEqual(expected_result, observed_ret_value)
hosted_agents = self.ctx.session.query( count_hosted_agents = network_obj.NetworkDhcpAgentBinding.count(
ndab_model.NetworkDhcpAgentBinding).all() self.ctx)
self.assertEqual(expected_hosted_agents, len(hosted_agents)) self.assertEqual(expected_hosted_agents, count_hosted_agents)
class TestAutoScheduleSegments(test_plugin.Ml2PluginV2TestCase, class TestAutoScheduleSegments(test_plugin.Ml2PluginV2TestCase,
@ -409,10 +410,11 @@ class TestNetworksFailover(TestDhcpSchedulerBaseTestCase,
sched_db.DhcpAgentSchedulerDbMixin, sched_db.DhcpAgentSchedulerDbMixin,
common_db_mixin.CommonDbMixin): common_db_mixin.CommonDbMixin):
def test_reschedule_network_from_down_agent(self): def test_reschedule_network_from_down_agent(self):
net_id = uuidutils.generate_uuid()
agents = self._create_and_set_agents_down(['host-a', 'host-b'], 1) agents = self._create_and_set_agents_down(['host-a', 'host-b'], 1)
self._test_schedule_bind_network([agents[0]], self.network_id) self._test_schedule_bind_network([agents[0]], self.network_id)
self._save_networks(["foo-network-2"]) self._save_networks([net_id])
self._test_schedule_bind_network([agents[1]], "foo-network-2") self._test_schedule_bind_network([agents[1]], net_id)
with mock.patch.object(self, 'remove_network_from_dhcp_agent') as rn,\ with mock.patch.object(self, 'remove_network_from_dhcp_agent') as rn,\
mock.patch.object(self, mock.patch.object(self,
'schedule_network', 'schedule_network',
@ -459,24 +461,70 @@ class TestNetworksFailover(TestDhcpSchedulerBaseTestCase,
rn_side_effect=dhcpagentscheduler.NetworkNotHostedByDhcpAgent( rn_side_effect=dhcpagentscheduler.NetworkNotHostedByDhcpAgent(
network_id='foo', agent_id='bar')) network_id='foo', agent_id='bar'))
def _create_test_networks(self, num_net=0):
networks = [network_obj.Network(
self.ctx,
id=uuidutils.generate_uuid(),
name='network-%s' % (i))
for i in range(num_net)]
for net in networks:
net.create()
return [net.id for net in networks]
def _create_dhcp_agents(self):
timestamp = datetime.datetime.now()
dhcp_agent_ids = [uuidutils.generate_uuid() for x in range(2)]
dhcp_agent_1 = agent.Agent(self.ctx, id=dhcp_agent_ids[0],
agent_type='DHCP Agent',
topic='fake_topic',
host='fake_host',
binary='fake_binary',
created_at=timestamp,
started_at=timestamp,
heartbeat_timestamp=timestamp,
configurations={},
load=0)
dhcp_agent_1.create()
dhcp_agent_2 = agent.Agent(self.ctx, id=dhcp_agent_ids[1],
agent_type='DHCP Agent',
topic='fake_topic',
host='fake_host_1',
binary='fake_binary',
created_at=timestamp,
started_at=timestamp,
heartbeat_timestamp=timestamp,
configurations={},
load=0)
dhcp_agent_2.create()
return [dhcp_agent_1.id, dhcp_agent_2.id]
def test_filter_bindings(self): def test_filter_bindings(self):
bindings = [ self.ctx = context.get_admin_context()
ndab_model.NetworkDhcpAgentBinding(network_id='foo1', dhcp_agt_ids = self._create_dhcp_agents()
dhcp_agent={'id': 'id1'}), network_ids = self._create_test_networks(num_net=4)
ndab_model.NetworkDhcpAgentBinding(network_id='foo2', ndab_obj1 = network_obj.NetworkDhcpAgentBinding(self.ctx,
dhcp_agent={'id': 'id1'}), network_id=network_ids[0], dhcp_agent_id=dhcp_agt_ids[0])
ndab_model.NetworkDhcpAgentBinding(network_id='foo3', ndab_obj1.create()
dhcp_agent={'id': 'id2'}), ndab_obj2 = network_obj.NetworkDhcpAgentBinding(self.ctx,
ndab_model.NetworkDhcpAgentBinding(network_id='foo4', network_id=network_ids[1], dhcp_agent_id=dhcp_agt_ids[0])
dhcp_agent={'id': 'id2'})] ndab_obj2.create()
ndab_obj3 = network_obj.NetworkDhcpAgentBinding(self.ctx,
network_id=network_ids[2], dhcp_agent_id=dhcp_agt_ids[1])
ndab_obj3.create()
ndab_obj4 = network_obj.NetworkDhcpAgentBinding(self.ctx,
network_id=network_ids[3], dhcp_agent_id=dhcp_agt_ids[1])
ndab_obj4.create()
bindings_objs = network_obj.NetworkDhcpAgentBinding.get_objects(
self.ctx)
with mock.patch.object(self, 'agent_starting_up', with mock.patch.object(self, 'agent_starting_up',
side_effect=[True, False]): side_effect=[True, False]):
res = [b for b in self._filter_bindings(None, bindings)] res = [b for b in self._filter_bindings(None, bindings_objs)]
# once per each agent id1 and id2 # once per each agent id1 and id2
self.assertEqual(2, len(res)) self.assertEqual(2, len(res))
res_ids = [b.network_id for b in res] res_ids = [b.network_id for b in res]
self.assertIn('foo3', res_ids) self.assertIn(network_ids[2], res_ids)
self.assertIn('foo4', res_ids) self.assertIn(network_ids[3], res_ids)
def test_reschedule_network_from_down_agent_failed_on_unexpected(self): def test_reschedule_network_from_down_agent_failed_on_unexpected(self):
agents = self._create_and_set_agents_down(['host-a'], 1) agents = self._create_and_set_agents_down(['host-a'], 1)
@ -717,7 +765,7 @@ class TestDhcpSchedulerFilter(TestDhcpSchedulerBaseTestCase,
admin_state_up=False) admin_state_up=False)
def test_get_dhcp_agents_hosting_many_networks(self): def test_get_dhcp_agents_hosting_many_networks(self):
net_id = 'another-net-id' net_id = uuidutils.generate_uuid()
self._save_networks([net_id]) self._save_networks([net_id])
networks = [net_id, self.network_id] networks = [net_id, self.network_id]
self._test_get_dhcp_agents_hosting_networks({'host-a', 'host-b', self._test_get_dhcp_agents_hosting_networks({'host-a', 'host-b',
@ -745,35 +793,38 @@ class DHCPAgentAZAwareWeightSchedulerTestCase(TestDhcpSchedulerBaseTestCase):
cfg.CONF.set_override("dhcp_load_type", "networks") cfg.CONF.set_override("dhcp_load_type", "networks")
def test_az_scheduler_one_az_hints(self): def test_az_scheduler_one_az_hints(self):
self._save_networks(['1111']) net_id = uuidutils.generate_uuid()
self._save_networks([net_id])
helpers.register_dhcp_agent('az1-host1', networks=1, az='az1') helpers.register_dhcp_agent('az1-host1', networks=1, az='az1')
helpers.register_dhcp_agent('az1-host2', networks=2, az='az1') helpers.register_dhcp_agent('az1-host2', networks=2, az='az1')
helpers.register_dhcp_agent('az2-host1', networks=3, az='az2') helpers.register_dhcp_agent('az2-host1', networks=3, az='az2')
helpers.register_dhcp_agent('az2-host2', networks=4, az='az2') helpers.register_dhcp_agent('az2-host2', networks=4, az='az2')
self.plugin.network_scheduler.schedule(self.plugin, self.ctx, self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
{'id': '1111', 'availability_zone_hints': ['az2']}) {'id': net_id, 'availability_zone_hints': ['az2']})
agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx, agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
['1111']) [net_id])
self.assertEqual(1, len(agents)) self.assertEqual(1, len(agents))
self.assertEqual('az2-host1', agents[0]['host']) self.assertEqual('az2-host1', agents[0]['host'])
def test_az_scheduler_default_az_hints(self): def test_az_scheduler_default_az_hints(self):
net_id = uuidutils.generate_uuid()
cfg.CONF.set_override('default_availability_zones', ['az1']) cfg.CONF.set_override('default_availability_zones', ['az1'])
self._save_networks(['1111']) self._save_networks([net_id])
helpers.register_dhcp_agent('az1-host1', networks=1, az='az1') helpers.register_dhcp_agent('az1-host1', networks=1, az='az1')
helpers.register_dhcp_agent('az1-host2', networks=2, az='az1') helpers.register_dhcp_agent('az1-host2', networks=2, az='az1')
helpers.register_dhcp_agent('az2-host1', networks=3, az='az2') helpers.register_dhcp_agent('az2-host1', networks=3, az='az2')
helpers.register_dhcp_agent('az2-host2', networks=4, az='az2') helpers.register_dhcp_agent('az2-host2', networks=4, az='az2')
self.plugin.network_scheduler.schedule(self.plugin, self.ctx, self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
{'id': '1111', 'availability_zone_hints': []}) {'id': net_id, 'availability_zone_hints': []})
agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx, agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
['1111']) [net_id])
self.assertEqual(1, len(agents)) self.assertEqual(1, len(agents))
self.assertEqual('az1-host1', agents[0]['host']) self.assertEqual('az1-host1', agents[0]['host'])
def test_az_scheduler_two_az_hints(self): def test_az_scheduler_two_az_hints(self):
net_id = uuidutils.generate_uuid()
cfg.CONF.set_override('dhcp_agents_per_network', 2) cfg.CONF.set_override('dhcp_agents_per_network', 2)
self._save_networks(['1111']) self._save_networks([net_id])
helpers.register_dhcp_agent('az1-host1', networks=1, az='az1') helpers.register_dhcp_agent('az1-host1', networks=1, az='az1')
helpers.register_dhcp_agent('az1-host2', networks=2, az='az1') helpers.register_dhcp_agent('az1-host2', networks=2, az='az1')
helpers.register_dhcp_agent('az2-host1', networks=3, az='az2') helpers.register_dhcp_agent('az2-host1', networks=3, az='az2')
@ -781,17 +832,18 @@ class DHCPAgentAZAwareWeightSchedulerTestCase(TestDhcpSchedulerBaseTestCase):
helpers.register_dhcp_agent('az3-host1', networks=5, az='az3') helpers.register_dhcp_agent('az3-host1', networks=5, az='az3')
helpers.register_dhcp_agent('az3-host2', networks=6, az='az3') helpers.register_dhcp_agent('az3-host2', networks=6, az='az3')
self.plugin.network_scheduler.schedule(self.plugin, self.ctx, self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
{'id': '1111', 'availability_zone_hints': ['az1', 'az3']}) {'id': net_id, 'availability_zone_hints': ['az1', 'az3']})
agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx, agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
['1111']) [net_id])
self.assertEqual(2, len(agents)) self.assertEqual(2, len(agents))
expected_hosts = set(['az1-host1', 'az3-host1']) expected_hosts = set(['az1-host1', 'az3-host1'])
hosts = set([a['host'] for a in agents]) hosts = set([a['host'] for a in agents])
self.assertEqual(expected_hosts, hosts) self.assertEqual(expected_hosts, hosts)
def test_az_scheduler_two_az_hints_one_available_az(self): def test_az_scheduler_two_az_hints_one_available_az(self):
net_id = uuidutils.generate_uuid()
cfg.CONF.set_override('dhcp_agents_per_network', 2) cfg.CONF.set_override('dhcp_agents_per_network', 2)
self._save_networks(['1111']) self._save_networks([net_id])
helpers.register_dhcp_agent('az1-host1', networks=1, az='az1') helpers.register_dhcp_agent('az1-host1', networks=1, az='az1')
helpers.register_dhcp_agent('az1-host2', networks=2, az='az1') helpers.register_dhcp_agent('az1-host2', networks=2, az='az1')
helpers.register_dhcp_agent('az2-host1', networks=3, alive=False, helpers.register_dhcp_agent('az2-host1', networks=3, alive=False,
@ -799,26 +851,27 @@ class DHCPAgentAZAwareWeightSchedulerTestCase(TestDhcpSchedulerBaseTestCase):
helpers.register_dhcp_agent('az2-host2', networks=4, helpers.register_dhcp_agent('az2-host2', networks=4,
admin_state_up=False, az='az2') admin_state_up=False, az='az2')
self.plugin.network_scheduler.schedule(self.plugin, self.ctx, self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
{'id': '1111', 'availability_zone_hints': ['az1', 'az2']}) {'id': net_id, 'availability_zone_hints': ['az1', 'az2']})
agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx, agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
['1111']) [net_id])
self.assertEqual(2, len(agents)) self.assertEqual(2, len(agents))
expected_hosts = set(['az1-host1', 'az1-host2']) expected_hosts = set(['az1-host1', 'az1-host2'])
hosts = set([a['host'] for a in agents]) hosts = set([a['host'] for a in agents])
self.assertEqual(expected_hosts, hosts) self.assertEqual(expected_hosts, hosts)
def _test_az_scheduler_no_az_hints(self, multiple_agent=False): def _test_az_scheduler_no_az_hints(self, multiple_agent=False):
net_id = uuidutils.generate_uuid()
num_agent = 2 if multiple_agent else 1 num_agent = 2 if multiple_agent else 1
cfg.CONF.set_override('dhcp_agents_per_network', num_agent) cfg.CONF.set_override('dhcp_agents_per_network', num_agent)
self._save_networks(['1111']) self._save_networks([net_id])
helpers.register_dhcp_agent('az1-host1', networks=2, az='az1') helpers.register_dhcp_agent('az1-host1', networks=2, az='az1')
helpers.register_dhcp_agent('az1-host2', networks=3, az='az1') helpers.register_dhcp_agent('az1-host2', networks=3, az='az1')
helpers.register_dhcp_agent('az2-host1', networks=2, az='az2') helpers.register_dhcp_agent('az2-host1', networks=2, az='az2')
helpers.register_dhcp_agent('az2-host2', networks=1, az='az2') helpers.register_dhcp_agent('az2-host2', networks=1, az='az2')
self.plugin.network_scheduler.schedule(self.plugin, self.ctx, self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
{'id': '1111', 'availability_zone_hints': []}) {'id': net_id, 'availability_zone_hints': []})
agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx, agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
['1111']) [net_id])
self.assertEqual(num_agent, len(agents)) self.assertEqual(num_agent, len(agents))
if multiple_agent: if multiple_agent:
expected_hosts = set(['az1-host1', 'az2-host2']) expected_hosts = set(['az1-host1', 'az2-host2'])
@ -834,7 +887,7 @@ class DHCPAgentAZAwareWeightSchedulerTestCase(TestDhcpSchedulerBaseTestCase):
self._test_az_scheduler_no_az_hints() self._test_az_scheduler_no_az_hints()
def test_az_scheduler_select_az_with_least_weight(self): def test_az_scheduler_select_az_with_least_weight(self):
self._save_networks(['1111']) self._save_networks([uuidutils.generate_uuid()])
dhcp_agents = [] dhcp_agents = []
# Register 6 dhcp agents in 3 AZs, every AZ will have 2 agents. # Register 6 dhcp agents in 3 AZs, every AZ will have 2 agents.
dhcp_agents.append( dhcp_agents.append(