Add agent scheduling for LBaaS namespace agent

- adds simple chance scheduling on create pool operation
- adds PoolsLoadbalancerAgentBinding db table
- adds lbaas_agentscheduler extension to list pools hosted by a particular agent
  and to get an agent hosting a particular pool
- adds agent notifiers mapping to AgentSchedulerDbMixin to make it easier
  for services to add their agent notifiers to the core plugin

Implements blueprint lbaas-agent-scheduler
Change-Id: Id98649fd5c7873dcd5be1a2b117b8bed25f06cc2
This commit is contained in:
Oleg Bondarev 2013-05-29 11:58:17 +04:00
parent f341ad5703
commit da65fe6951
27 changed files with 945 additions and 152 deletions

View File

@ -220,6 +220,8 @@ notification_topics = notifications
# network_scheduler_driver = neutron.scheduler.dhcp_agent_scheduler.ChanceScheduler # network_scheduler_driver = neutron.scheduler.dhcp_agent_scheduler.ChanceScheduler
# Driver to use for scheduling router to a default L3 agent # Driver to use for scheduling router to a default L3 agent
# router_scheduler_driver = neutron.scheduler.l3_agent_scheduler.ChanceScheduler # router_scheduler_driver = neutron.scheduler.l3_agent_scheduler.ChanceScheduler
# Driver to use for scheduling a loadbalancer pool to an lbaas agent
# loadbalancer_pool_scheduler_driver = neutron.services.loadbalancer.agent_scheduler.ChanceScheduler
# Allow auto scheduling networks to DHCP agent. It will schedule non-hosted # Allow auto scheduling networks to DHCP agent. It will schedule non-hosted
# networks to first DHCP agent which sends get_active_networks message to # networks to first DHCP agent which sends get_active_networks message to

View File

@ -79,6 +79,8 @@
"get_l3-routers": "rule:admin_only", "get_l3-routers": "rule:admin_only",
"get_dhcp-agents": "rule:admin_only", "get_dhcp-agents": "rule:admin_only",
"get_l3-agents": "rule:admin_only", "get_l3-agents": "rule:admin_only",
"get_loadbalancer-agent": "rule:admin_only",
"get_loadbalancer-pools": "rule:admin_only",
"create_router": "rule:regular_user", "create_router": "rule:regular_user",
"get_router": "rule:admin_or_owner", "get_router": "rule:admin_or_owner",

View File

@ -67,6 +67,7 @@ AGENT_TYPE_OVS = 'Open vSwitch agent'
AGENT_TYPE_LINUXBRIDGE = 'Linux bridge agent' AGENT_TYPE_LINUXBRIDGE = 'Linux bridge agent'
AGENT_TYPE_NEC = 'NEC plugin agent' AGENT_TYPE_NEC = 'NEC plugin agent'
AGENT_TYPE_L3 = 'L3 agent' AGENT_TYPE_L3 = 'L3 agent'
AGENT_TYPE_LOADBALANCER = 'Loadbalancer agent'
L2_AGENT_TOPIC = 'N/A' L2_AGENT_TOPIC = 'N/A'
PAGINATION_INFINITE = 'infinite' PAGINATION_INFINITE = 'infinite'
@ -76,3 +77,4 @@ SORT_DIRECTION_DESC = 'desc'
L3_AGENT_SCHEDULER_EXT_ALIAS = 'l3_agent_scheduler' L3_AGENT_SCHEDULER_EXT_ALIAS = 'l3_agent_scheduler'
DHCP_AGENT_SCHEDULER_EXT_ALIAS = 'dhcp_agent_scheduler' DHCP_AGENT_SCHEDULER_EXT_ALIAS = 'dhcp_agent_scheduler'
LBAAS_AGENT_SCHEDULER_EXT_ALIAS = 'lbaas_agent_scheduler'

View File

@ -162,6 +162,9 @@ class AgentExtRpcCallback(object):
RPC_API_VERSION = '1.0' RPC_API_VERSION = '1.0'
START_TIME = timeutils.utcnow() START_TIME = timeutils.utcnow()
def __init__(self, plugin=None):
self.plugin = plugin
def report_state(self, context, **kwargs): def report_state(self, context, **kwargs):
"""Report state from agent to server.""" """Report state from agent to server."""
time = kwargs['time'] time = kwargs['time']
@ -170,5 +173,6 @@ class AgentExtRpcCallback(object):
LOG.debug(_("Message with invalid timestamp received")) LOG.debug(_("Message with invalid timestamp received"))
return return
agent_state = kwargs['agent_state']['agent_state'] agent_state = kwargs['agent_state']['agent_state']
plugin = manager.NeutronManager.get_plugin() if not self.plugin:
plugin.create_or_update_agent(context, agent_state) self.plugin = manager.NeutronManager.get_plugin()
self.plugin.create_or_update_agent(context, agent_state)

View File

@ -79,8 +79,13 @@ class RouterL3AgentBinding(model_base.BASEV2, models_v2.HasId):
class AgentSchedulerDbMixin(agents_db.AgentDbMixin): class AgentSchedulerDbMixin(agents_db.AgentDbMixin):
"""Common class for agent scheduler mixins.""" """Common class for agent scheduler mixins."""
dhcp_agent_notifier = None # agent notifiers to handle agent update operations;
l3_agent_notifier = None # should be updated by plugins;
agent_notifiers = {
constants.AGENT_TYPE_DHCP: None,
constants.AGENT_TYPE_L3: None,
constants.AGENT_TYPE_LOADBALANCER: None,
}
@staticmethod @staticmethod
def is_eligible_agent(active, agent): def is_eligible_agent(active, agent):
@ -100,18 +105,13 @@ class AgentSchedulerDbMixin(agents_db.AgentDbMixin):
result = super(AgentSchedulerDbMixin, self).update_agent( result = super(AgentSchedulerDbMixin, self).update_agent(
context, id, agent) context, id, agent)
agent_data = agent['agent'] agent_data = agent['agent']
if ('admin_state_up' in agent_data and agent_notifier = self.agent_notifiers.get(original_agent['agent_type'])
if (agent_notifier and
'admin_state_up' in agent_data and
original_agent['admin_state_up'] != agent_data['admin_state_up']): original_agent['admin_state_up'] != agent_data['admin_state_up']):
if (original_agent['agent_type'] == constants.AGENT_TYPE_DHCP and agent_notifier.agent_updated(context,
self.dhcp_agent_notifier): agent_data['admin_state_up'],
self.dhcp_agent_notifier.agent_updated( original_agent['host'])
context, agent_data['admin_state_up'],
original_agent['host'])
elif (original_agent['agent_type'] == constants.AGENT_TYPE_L3 and
self.l3_agent_notifier):
self.l3_agent_notifier.agent_updated(
context, agent_data['admin_state_up'],
original_agent['host'])
return result return result
@ -148,8 +148,9 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
raise l3agentscheduler.RouterSchedulingFailed( raise l3agentscheduler.RouterSchedulingFailed(
router_id=router_id, agent_id=id) router_id=router_id, agent_id=id)
if self.l3_agent_notifier: l3_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_L3)
self.l3_agent_notifier.router_added_to_agent( if l3_notifier:
l3_notifier.router_added_to_agent(
context, [router_id], agent_db.host) context, [router_id], agent_db.host)
def remove_router_from_l3_agent(self, context, id, router_id): def remove_router_from_l3_agent(self, context, id, router_id):
@ -170,8 +171,9 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
raise l3agentscheduler.RouterNotHostedByL3Agent( raise l3agentscheduler.RouterNotHostedByL3Agent(
router_id=router_id, agent_id=id) router_id=router_id, agent_id=id)
context.session.delete(binding) context.session.delete(binding)
if self.l3_agent_notifier: l3_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_L3)
self.l3_agent_notifier.router_removed_from_agent( if l3_notifier:
l3_notifier.router_removed_from_agent(
context, router_id, agent.host) context, router_id, agent.host)
def list_routers_on_l3_agent(self, context, id): def list_routers_on_l3_agent(self, context, id):
@ -356,8 +358,9 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
binding.dhcp_agent_id = id binding.dhcp_agent_id = id
binding.network_id = network_id binding.network_id = network_id
context.session.add(binding) context.session.add(binding)
if self.dhcp_agent_notifier: dhcp_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_DHCP)
self.dhcp_agent_notifier.network_added_to_agent( if dhcp_notifier:
dhcp_notifier.network_added_to_agent(
context, network_id, agent_db.host) context, network_id, agent_db.host)
def remove_network_from_dhcp_agent(self, context, id, network_id): def remove_network_from_dhcp_agent(self, context, id, network_id):
@ -372,8 +375,9 @@ class DhcpAgentSchedulerDbMixin(dhcpagentscheduler
raise dhcpagentscheduler.NetworkNotHostedByDhcpAgent( raise dhcpagentscheduler.NetworkNotHostedByDhcpAgent(
network_id=network_id, agent_id=id) network_id=network_id, agent_id=id)
context.session.delete(binding) context.session.delete(binding)
if self.dhcp_agent_notifier: dhcp_notifier = self.agent_notifiers.get(constants.AGENT_TYPE_DHCP)
self.dhcp_agent_notifier.network_removed_from_agent( if dhcp_notifier:
dhcp_notifier.network_removed_from_agent(
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):

View File

@ -0,0 +1,53 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2013 OpenStack Foundation
#
# 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.
#
"""LBaaS Pool scheduler
Revision ID: 52c5e4a18807
Revises: 2032abe8edac
Create Date: 2013-06-14 03:23:47.815865
"""
# revision identifiers, used by Alembic.
revision = '52c5e4a18807'
down_revision = '2032abe8edac'
from alembic import op
import sqlalchemy as sa
def upgrade(active_plugin=None, options=None):
### commands auto generated by Alembic - please adjust! ###
op.create_table(
'poolloadbalanceragentbindings',
sa.Column('pool_id', sa.String(length=36), nullable=False),
sa.Column('loadbalancer_agent_id', sa.String(length=36),
nullable=False),
sa.ForeignKeyConstraint(['loadbalancer_agent_id'], ['agents.id'],
ondelete='CASCADE'),
sa.ForeignKeyConstraint(['pool_id'], ['pools.id'],
ondelete='CASCADE'),
sa.PrimaryKeyConstraint('pool_id')
)
### end Alembic commands ###
def downgrade(active_plugin=None, options=None):
### commands auto generated by Alembic - please adjust! ###
op.drop_table('poolloadbalanceragentbindings')
### end Alembic commands ###

View File

@ -0,0 +1,138 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2013 OpenStack Foundation.
# All rights reserved.
#
# 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.
from abc import abstractmethod
from neutron.api import extensions
from neutron.api.v2 import base
from neutron.api.v2 import resource
from neutron.common import constants
from neutron.extensions import agent
from neutron import manager
from neutron.plugins.common import constants as plugin_const
from neutron import policy
from neutron import wsgi
LOADBALANCER_POOL = 'loadbalancer-pool'
LOADBALANCER_POOLS = LOADBALANCER_POOL + 's'
LOADBALANCER_AGENT = 'loadbalancer-agent'
class PoolSchedulerController(wsgi.Controller):
def index(self, request, **kwargs):
lbaas_plugin = manager.NeutronManager.get_service_plugins().get(
plugin_const.LOADBALANCER)
if not lbaas_plugin:
return {'pools': []}
policy.enforce(request.context,
"get_%s" % LOADBALANCER_POOLS,
{},
plugin=lbaas_plugin)
return lbaas_plugin.list_pools_on_lbaas_agent(
request.context, kwargs['agent_id'])
class LbaasAgentHostingPoolController(wsgi.Controller):
def index(self, request, **kwargs):
lbaas_plugin = manager.NeutronManager.get_service_plugins().get(
plugin_const.LOADBALANCER)
if not lbaas_plugin:
return
policy.enforce(request.context,
"get_%s" % LOADBALANCER_AGENT,
{},
plugin=lbaas_plugin)
return lbaas_plugin.get_lbaas_agent_hosting_pool(
request.context, kwargs['pool_id'])
class Lbaas_agentscheduler(extensions.ExtensionDescriptor):
"""Extension class supporting l3 agent scheduler.
"""
@classmethod
def get_name(cls):
return "Loadbalancer Agent Scheduler"
@classmethod
def get_alias(cls):
return constants.LBAAS_AGENT_SCHEDULER_EXT_ALIAS
@classmethod
def get_description(cls):
return "Schedule pools among lbaas agents"
@classmethod
def get_namespace(cls):
return "http://docs.openstack.org/ext/lbaas_agent_scheduler/api/v1.0"
@classmethod
def get_updated(cls):
return "2013-02-07T10:00:00-00:00"
@classmethod
def get_resources(cls):
"""Returns Ext Resources."""
exts = []
parent = dict(member_name="agent",
collection_name="agents")
controller = resource.Resource(PoolSchedulerController(),
base.FAULT_MAP)
exts.append(extensions.ResourceExtension(
LOADBALANCER_POOLS, controller, parent))
parent = dict(member_name="pool",
collection_name="pools")
controller = resource.Resource(LbaasAgentHostingPoolController(),
base.FAULT_MAP)
exts.append(extensions.ResourceExtension(
LOADBALANCER_AGENT, controller, parent,
path_prefix=plugin_const.
COMMON_PREFIXES[plugin_const.LOADBALANCER]))
return exts
def get_extended_resources(self, version):
return {}
class NoEligibleLbaasAgent(agent.AgentNotFound):
message = _("No eligible loadbalancer agent found "
"for pool %(pool_id)s.")
class NoActiveLbaasAgent(agent.AgentNotFound):
message = _("No active loadbalancer agent found "
"for pool %(pool_id)s.")
class LbaasAgentSchedulerPluginBase(object):
"""REST API to operate the lbaas agent scheduler.
All of method must be in an admin context.
"""
@abstractmethod
def list_pools_on_lbaas_agent(self, context, id):
pass
@abstractmethod
def get_lbaas_agent_hosting_pool(self, context, pool_id):
pass

View File

@ -177,6 +177,12 @@ class NeutronManager(object):
self.service_plugins[plugin_inst.get_plugin_type()] = plugin_inst self.service_plugins[plugin_inst.get_plugin_type()] = plugin_inst
# search for possible agent notifiers declared in service plugin
# (needed by agent management extension)
if (hasattr(self.plugin, 'agent_notifiers') and
hasattr(plugin_inst, 'agent_notifiers')):
self.plugin.agent_notifiers.update(plugin_inst.agent_notifiers)
LOG.debug(_("Successfully loaded %(type)s plugin. " LOG.debug(_("Successfully loaded %(type)s plugin. "
"Description: %(desc)s"), "Description: %(desc)s"),
{"type": plugin_inst.get_plugin_type(), {"type": plugin_inst.get_plugin_type(),

View File

@ -30,6 +30,7 @@ from oslo.config import cfg
from neutron.agent import securitygroups_rpc as sg_rpc from neutron.agent import securitygroups_rpc as sg_rpc
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
from neutron.common import constants as q_const
from neutron.common import rpc as q_rpc from neutron.common import rpc as q_rpc
from neutron.common import topics from neutron.common import topics
from neutron.common import utils from neutron.common import utils
@ -254,8 +255,12 @@ class BrocadePluginV2(db_base_plugin_v2.NeutronDbPluginV2,
# Consume from all consumers in a thread # Consume from all consumers in a thread
self.conn.consume_in_thread() self.conn.consume_in_thread()
self.notifier = AgentNotifierApi(topics.AGENT) self.notifier = AgentNotifierApi(topics.AGENT)
self.dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI() self.agent_notifiers[q_const.AGENT_TYPE_DHCP] = (
self.l3_agent_notifier = l3_rpc_agent_api.L3AgentNotify dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
)
self.agent_notifiers[q_const.AGENT_TYPE_L3] = (
l3_rpc_agent_api.L3AgentNotify
)
def create_network(self, context, network): def create_network(self, context, network):
"""Create network. """Create network.

View File

@ -268,8 +268,12 @@ class LinuxBridgePluginV2(db_base_plugin_v2.NeutronDbPluginV2,
# Consume from all consumers in a thread # Consume from all consumers in a thread
self.conn.consume_in_thread() self.conn.consume_in_thread()
self.notifier = AgentNotifierApi(topics.AGENT) self.notifier = AgentNotifierApi(topics.AGENT)
self.dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI() self.agent_notifiers[q_const.AGENT_TYPE_DHCP] = (
self.l3_agent_notifier = l3_rpc_agent_api.L3AgentNotify dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
)
self.agent_notifiers[q_const.AGENT_TYPE_L3] = (
l3_rpc_agent_api.L3AgentNotify
)
def _parse_network_vlan_ranges(self): def _parse_network_vlan_ranges(self):
try: try:

View File

@ -107,8 +107,12 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
def _setup_rpc(self): def _setup_rpc(self):
self.notifier = rpc.AgentNotifierApi(topics.AGENT) self.notifier = rpc.AgentNotifierApi(topics.AGENT)
self.dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI() self.agent_notifiers[const.AGENT_TYPE_DHCP] = (
self.l3_agent_notifier = l3_rpc_agent_api.L3AgentNotify dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
)
self.agent_notifiers[const.AGENT_TYPE_L3] = (
l3_rpc_agent_api.L3AgentNotify
)
self.callbacks = rpc.RpcCallbacks(self.notifier, self.type_manager) self.callbacks = rpc.RpcCallbacks(self.notifier, self.type_manager)
self.topic = topics.PLUGIN self.topic = topics.PLUGIN
self.conn = c_rpc.create_connection(new=True) self.conn = c_rpc.create_connection(new=True)

View File

@ -19,6 +19,7 @@
from neutron.agent import securitygroups_rpc as sg_rpc from neutron.agent import securitygroups_rpc as sg_rpc
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
from neutron.common import constants as q_const
from neutron.common import exceptions as q_exc from neutron.common import exceptions as q_exc
from neutron.common import rpc as q_rpc from neutron.common import rpc as q_rpc
from neutron.common import topics from neutron.common import topics
@ -119,8 +120,12 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
self.topic = topics.PLUGIN self.topic = topics.PLUGIN
self.conn = rpc.create_connection(new=True) self.conn = rpc.create_connection(new=True)
self.notifier = NECPluginV2AgentNotifierApi(topics.AGENT) self.notifier = NECPluginV2AgentNotifierApi(topics.AGENT)
self.dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI() self.agent_notifiers[q_const.AGENT_TYPE_DHCP] = (
self.l3_agent_notifier = l3_rpc_agent_api.L3AgentNotify dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
)
self.agent_notifiers[q_const.AGENT_TYPE_L3] = (
l3_rpc_agent_api.L3AgentNotify
)
# NOTE: callback_sg is referred to from the sg unit test. # NOTE: callback_sg is referred to from the sg unit test.
self.callback_sg = SecurityGroupServerRpcCallback() self.callback_sg = SecurityGroupServerRpcCallback()

View File

@ -819,7 +819,8 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
self.dispatcher = NVPRpcCallbacks().create_rpc_dispatcher() self.dispatcher = NVPRpcCallbacks().create_rpc_dispatcher()
self.conn.create_consumer(self.topic, self.dispatcher, self.conn.create_consumer(self.topic, self.dispatcher,
fanout=False) fanout=False)
self.dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI() self.agent_notifiers[constants.AGENT_TYPE_DHCP] = (
dhcp_rpc_agent_api.DhcpAgentNotifyAPI())
# Consume from all consumers in a thread # Consume from all consumers in a thread
self.conn.consume_in_thread() self.conn.consume_in_thread()

View File

@ -309,8 +309,12 @@ class OVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
self.topic = topics.PLUGIN self.topic = topics.PLUGIN
self.conn = rpc.create_connection(new=True) self.conn = rpc.create_connection(new=True)
self.notifier = AgentNotifierApi(topics.AGENT) self.notifier = AgentNotifierApi(topics.AGENT)
self.dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI() self.agent_notifiers[q_const.AGENT_TYPE_DHCP] = (
self.l3_agent_notifier = l3_rpc_agent_api.L3AgentNotify dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
)
self.agent_notifiers[q_const.AGENT_TYPE_L3] = (
l3_rpc_agent_api.L3AgentNotify
)
self.callbacks = OVSRpcCallbacks(self.notifier, self.tunnel_type) self.callbacks = OVSRpcCallbacks(self.notifier, self.tunnel_type)
self.dispatcher = self.callbacks.create_rpc_dispatcher() self.dispatcher = self.callbacks.create_rpc_dispatcher()
self.conn.create_consumer(self.topic, self.dispatcher, self.conn.create_consumer(self.topic, self.dispatcher,

View File

@ -0,0 +1,114 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2013 OpenStack Foundation.
# All Rights Reserved.
#
# 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 random
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import joinedload
from neutron.common import constants
from neutron.db import agents_db
from neutron.db import agentschedulers_db
from neutron.db import model_base
from neutron.extensions import lbaas_agentscheduler
from neutron.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class PoolLoadbalancerAgentBinding(model_base.BASEV2):
"""Represents binding between neutron loadbalancer pools and agents."""
pool_id = sa.Column(sa.String(36),
sa.ForeignKey("pools.id", ondelete='CASCADE'),
primary_key=True)
agent = orm.relation(agents_db.Agent)
agent_id = sa.Column(sa.String(36), sa.ForeignKey("agents.id",
ondelete='CASCADE'))
class LbaasAgentSchedulerDbMixin(agentschedulers_db.AgentSchedulerDbMixin,
lbaas_agentscheduler
.LbaasAgentSchedulerPluginBase):
def get_lbaas_agent_hosting_pool(self, context, pool_id, active=None):
query = context.session.query(PoolLoadbalancerAgentBinding)
query = query.options(joinedload('agent'))
binding = query.get(pool_id)
if (binding and self.is_eligible_agent(
active, binding.agent)):
return {'agent': self._make_agent_dict(binding.agent)}
def get_lbaas_agents(self, context, active=None, filters=None):
query = context.session.query(agents_db.Agent)
query = query.filter_by(agent_type=constants.AGENT_TYPE_LOADBALANCER)
if active is not None:
query = query.filter_by(admin_state_up=active)
if filters:
for key, value in filters.iteritems():
column = getattr(agents_db.Agent, key, None)
if column:
query = query.filter(column.in_(value))
return [agent
for agent in query
if self.is_eligible_agent(active, agent)]
def list_pools_on_lbaas_agent(self, context, id):
query = context.session.query(PoolLoadbalancerAgentBinding.pool_id)
query = query.filter_by(agent_id=id)
pool_ids = [item[0] for item in query]
if pool_ids:
return {'pools': self.get_pools(context, filters={'id': pool_ids})}
else:
return {'pools': []}
class ChanceScheduler(object):
"""Allocate a loadbalancer agent for a vip in a random way."""
def schedule(self, plugin, context, pool):
"""Schedule the pool to an active loadbalancer agent if there
is no enabled agent hosting it.
"""
with context.session.begin(subtransactions=True):
lbaas_agent = plugin.get_lbaas_agent_hosting_pool(
context, pool['id'])
if lbaas_agent:
LOG.debug(_('Pool %(pool_id)s has already been hosted'
' by lbaas agent %(agent_id)s'),
{'pool_id': pool['id'],
'agent_id': lbaas_agent['id']})
return
candidates = plugin.get_lbaas_agents(context, active=True)
if not candidates:
LOG.warn(_('No active lbaas agents for pool %s') % pool['id'])
return
chosen_agent = random.choice(candidates)
binding = PoolLoadbalancerAgentBinding()
binding.agent = chosen_agent
binding.pool_id = pool['id']
context.session.add(binding)
LOG.debug(_('Pool %(pool_id)s is scheduled to '
'lbaas agent %(agent_id)s'),
{'pool_id': pool['id'],
'agent_id': chosen_agent['id']})
return chosen_agent

View File

@ -55,6 +55,7 @@ def main():
cfg.CONF.register_opts(manager.OPTS) cfg.CONF.register_opts(manager.OPTS)
# import interface options just in case the driver uses namespaces # import interface options just in case the driver uses namespaces
cfg.CONF.register_opts(interface.OPTS) cfg.CONF.register_opts(interface.OPTS)
config.register_agent_state_opts_helper(cfg.CONF)
config.register_root_helper(cfg.CONF) config.register_root_helper(cfg.CONF)
cfg.CONF(project='neutron') cfg.CONF(project='neutron')

View File

@ -21,9 +21,12 @@ import weakref
from oslo.config import cfg from oslo.config import cfg
from neutron.agent.common import config from neutron.agent.common import config
from neutron.agent import rpc as agent_rpc
from neutron.common import constants
from neutron import context from neutron import context
from neutron.openstack.common import importutils from neutron.openstack.common import importutils
from neutron.openstack.common import log as logging from neutron.openstack.common import log as logging
from neutron.openstack.common import loopingcall
from neutron.openstack.common import periodic_task from neutron.openstack.common import periodic_task
from neutron.services.loadbalancer.drivers.haproxy import ( from neutron.services.loadbalancer.drivers.haproxy import (
agent_api, agent_api,
@ -110,6 +113,12 @@ class LogicalDeviceCache(object):
class LbaasAgentManager(periodic_task.PeriodicTasks): class LbaasAgentManager(periodic_task.PeriodicTasks):
# history
# 1.0 Initial version
# 1.1 Support agent_updated call
RPC_API_VERSION = '1.1'
def __init__(self, conf): def __init__(self, conf):
self.conf = conf self.conf = conf
try: try:
@ -131,15 +140,46 @@ class LbaasAgentManager(periodic_task.PeriodicTasks):
except ImportError: except ImportError:
msg = _('Error importing loadbalancer device driver: %s') msg = _('Error importing loadbalancer device driver: %s')
raise SystemExit(msg % conf.device_driver) raise SystemExit(msg % conf.device_driver)
ctx = context.get_admin_context_without_session()
self.plugin_rpc = agent_api.LbaasAgentApi( self.agent_state = {
plugin_driver.TOPIC_PROCESS_ON_HOST, 'binary': 'neutron-loadbalancer-agent',
ctx, 'host': conf.host,
conf.host 'topic': plugin_driver.TOPIC_LOADBALANCER_AGENT,
) 'configurations': {'device_driver': conf.device_driver,
'interface_driver': conf.interface_driver},
'agent_type': constants.AGENT_TYPE_LOADBALANCER,
'start_flag': True}
self.admin_state_up = True
self.context = context.get_admin_context_without_session()
self._setup_rpc()
self.needs_resync = False self.needs_resync = False
self.cache = LogicalDeviceCache() self.cache = LogicalDeviceCache()
def _setup_rpc(self):
self.plugin_rpc = agent_api.LbaasAgentApi(
plugin_driver.TOPIC_PROCESS_ON_HOST,
self.context,
self.conf.host
)
self.state_rpc = agent_rpc.PluginReportStateAPI(
plugin_driver.TOPIC_PROCESS_ON_HOST)
report_interval = self.conf.AGENT.report_interval
if report_interval:
heartbeat = loopingcall.FixedIntervalLoopingCall(
self._report_state)
heartbeat.start(interval=report_interval)
def _report_state(self):
try:
device_count = len(self.cache.devices)
self.agent_state['configurations']['devices'] = device_count
self.state_rpc.report_state(self.context,
self.agent_state)
self.agent_state.pop('start_flag', None)
except Exception:
LOG.exception("Failed reporting state!")
def initialize_service_hook(self, started_by): def initialize_service_hook(self, started_by):
self.sync_state() self.sync_state()
@ -228,3 +268,14 @@ class LbaasAgentManager(periodic_task.PeriodicTasks):
"""Handle RPC cast from plugin to destroy a pool if known to agent.""" """Handle RPC cast from plugin to destroy a pool if known to agent."""
if self.cache.get_by_pool_id(pool_id): if self.cache.get_by_pool_id(pool_id):
self.destroy_device(pool_id) self.destroy_device(pool_id)
def agent_updated(self, context, payload):
"""Handle the agent_updated notification event."""
if payload['admin_state_up'] != self.admin_state_up:
self.admin_state_up = payload['admin_state_up']
if self.admin_state_up:
self.needs_resync = True
else:
for pool_id in self.cache.get_pool_ids():
self.destroy_device(pool_id)
LOG.info(_("agent_updated by server side %s!"), payload)

View File

@ -20,9 +20,13 @@ import uuid
from oslo.config import cfg from oslo.config import cfg
from neutron.common import constants as q_const
from neutron.common import exceptions as q_exc from neutron.common import exceptions as q_exc
from neutron.common import rpc as q_rpc from neutron.common import rpc as q_rpc
from neutron.db import agents_db
from neutron.db.loadbalancer import loadbalancer_db from neutron.db.loadbalancer import loadbalancer_db
from neutron.extensions import lbaas_agentscheduler
from neutron.openstack.common import importutils
from neutron.openstack.common import log as logging from neutron.openstack.common import log as logging
from neutron.openstack.common import rpc from neutron.openstack.common import rpc
from neutron.openstack.common.rpc import proxy from neutron.openstack.common.rpc import proxy
@ -37,19 +41,31 @@ ACTIVE_PENDING = (
constants.PENDING_UPDATE constants.PENDING_UPDATE
) )
AGENT_SCHEDULER_OPTS = [
cfg.StrOpt('loadbalancer_pool_scheduler_driver',
default='neutron.services.loadbalancer.agent_scheduler'
'.ChanceScheduler',
help=_('Driver to use for scheduling '
'pool to a default loadbalancer agent')),
]
cfg.CONF.register_opts(AGENT_SCHEDULER_OPTS)
# topic name for this particular agent implementation # topic name for this particular agent implementation
TOPIC_PROCESS_ON_HOST = 'q-lbaas-process-on-host' TOPIC_PROCESS_ON_HOST = 'q-lbaas-process-on-host'
TOPIC_LOADBALANCER_AGENT = 'lbaas_process_on_host_agent' TOPIC_LOADBALANCER_AGENT = 'lbaas_process_on_host_agent'
class LoadBalancerCallbacks(object): class LoadBalancerCallbacks(object):
RPC_API_VERSION = '1.0' RPC_API_VERSION = '1.0'
def __init__(self, plugin): def __init__(self, plugin):
self.plugin = plugin self.plugin = plugin
def create_rpc_dispatcher(self): def create_rpc_dispatcher(self):
return q_rpc.PluginRpcDispatcher([self]) return q_rpc.PluginRpcDispatcher(
[self, agents_db.AgentExtRpcCallback(self.plugin)])
def get_ready_devices(self, context, host=None): def get_ready_devices(self, context, host=None):
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
@ -61,6 +77,17 @@ class LoadBalancerCallbacks(object):
up = True # makes pep8 and sqlalchemy happy up = True # makes pep8 and sqlalchemy happy
qry = qry.filter(loadbalancer_db.Vip.admin_state_up == up) qry = qry.filter(loadbalancer_db.Vip.admin_state_up == up)
qry = qry.filter(loadbalancer_db.Pool.admin_state_up == up) qry = qry.filter(loadbalancer_db.Pool.admin_state_up == up)
agents = self.plugin.get_lbaas_agents(context,
filters={'host': [host]})
if not agents:
return []
elif len(agents) > 1:
LOG.warning(_('Multiple lbaas agents found on host %s') % host)
pools = self.plugin.list_pools_on_lbaas_agent(context,
agents[0].id)
pool_ids = [pool['id'] for pool in pools['pools']]
qry = qry.filter(loadbalancer_db.Pool.id.in_(pool_ids))
return [id for id, in qry] return [id for id, in qry]
def get_logical_device(self, context, pool_id=None, activate=True, def get_logical_device(self, context, pool_id=None, activate=True,
@ -185,40 +212,50 @@ class LoadBalancerCallbacks(object):
class LoadBalancerAgentApi(proxy.RpcProxy): class LoadBalancerAgentApi(proxy.RpcProxy):
"""Plugin side of plugin to agent RPC API.""" """Plugin side of plugin to agent RPC API."""
API_VERSION = '1.0' BASE_RPC_API_VERSION = '1.0'
# history
# 1.0 Initial version
# 1.1 Support agent_updated call
def __init__(self, topic, host): def __init__(self, topic):
super(LoadBalancerAgentApi, self).__init__(topic, self.API_VERSION) super(LoadBalancerAgentApi, self).__init__(
self.host = host topic, default_version=self.BASE_RPC_API_VERSION)
def reload_pool(self, context, pool_id): def reload_pool(self, context, pool_id, host):
return self.cast( return self.cast(
context, context,
self.make_msg('reload_pool', pool_id=pool_id, host=self.host), self.make_msg('reload_pool', pool_id=pool_id, host=host),
topic=self.topic topic='%s.%s' % (self.topic, host)
) )
def destroy_pool(self, context, pool_id): def destroy_pool(self, context, pool_id, host):
return self.cast( return self.cast(
context, context,
self.make_msg('destroy_pool', pool_id=pool_id, host=self.host), self.make_msg('destroy_pool', pool_id=pool_id, host=host),
topic=self.topic topic='%s.%s' % (self.topic, host)
) )
def modify_pool(self, context, pool_id): def modify_pool(self, context, pool_id, host):
return self.cast( return self.cast(
context, context,
self.make_msg('modify_pool', pool_id=pool_id, host=self.host), self.make_msg('modify_pool', pool_id=pool_id, host=host),
topic=self.topic topic='%s.%s' % (self.topic, host)
)
def agent_updated(self, context, admin_state_up, host):
return self.cast(
context,
self.make_msg('agent_updated',
payload={'admin_state_up': admin_state_up}),
topic='%s.%s' % (self.topic, host),
version='1.1'
) )
class HaproxyOnHostPluginDriver(abstract_driver.LoadBalancerAbstractDriver): class HaproxyOnHostPluginDriver(abstract_driver.LoadBalancerAbstractDriver):
def __init__(self, plugin): def __init__(self, plugin):
self.agent_rpc = LoadBalancerAgentApi( self.agent_rpc = LoadBalancerAgentApi(TOPIC_LOADBALANCER_AGENT)
TOPIC_LOADBALANCER_AGENT,
cfg.CONF.host
)
self.callbacks = LoadBalancerCallbacks(plugin) self.callbacks = LoadBalancerCallbacks(plugin)
self.conn = rpc.create_connection(new=True) self.conn = rpc.create_connection(new=True)
@ -228,56 +265,85 @@ class HaproxyOnHostPluginDriver(abstract_driver.LoadBalancerAbstractDriver):
fanout=False) fanout=False)
self.conn.consume_in_thread() self.conn.consume_in_thread()
self.plugin = plugin self.plugin = plugin
self.plugin.agent_notifiers.update(
{q_const.AGENT_TYPE_LOADBALANCER: self.agent_rpc})
self.pool_scheduler = importutils.import_object(
cfg.CONF.loadbalancer_pool_scheduler_driver)
def get_pool_agent(self, context, pool_id):
agent = self.plugin.get_lbaas_agent_hosting_pool(context, pool_id)
if not agent:
raise lbaas_agentscheduler.NoActiveLbaasAgent(pool_id=pool_id)
return agent['agent']
def create_vip(self, context, vip): def create_vip(self, context, vip):
self.agent_rpc.reload_pool(context, vip['pool_id']) agent = self.get_pool_agent(context, vip['pool_id'])
self.agent_rpc.reload_pool(context, vip['pool_id'], agent['host'])
def update_vip(self, context, old_vip, vip): def update_vip(self, context, old_vip, vip):
agent = self.get_pool_agent(context, vip['pool_id'])
if vip['status'] in ACTIVE_PENDING: if vip['status'] in ACTIVE_PENDING:
self.agent_rpc.reload_pool(context, vip['pool_id']) self.agent_rpc.reload_pool(context, vip['pool_id'], agent['host'])
else: else:
self.agent_rpc.destroy_pool(context, vip['pool_id']) self.agent_rpc.destroy_pool(context, vip['pool_id'], agent['host'])
def delete_vip(self, context, vip): def delete_vip(self, context, vip):
self.plugin._delete_db_vip(context, vip['id']) self.plugin._delete_db_vip(context, vip['id'])
self.agent_rpc.destroy_pool(context, vip['pool_id']) agent = self.get_pool_agent(context, vip['pool_id'])
self.agent_rpc.destroy_pool(context, vip['pool_id'], agent['host'])
def create_pool(self, context, pool): def create_pool(self, context, pool):
if not self.pool_scheduler.schedule(self.plugin, context, pool):
raise lbaas_agentscheduler.NoEligibleLbaasAgent(pool_id=pool['id'])
# don't notify here because a pool needs a vip to be useful # don't notify here because a pool needs a vip to be useful
pass
def update_pool(self, context, old_pool, pool): def update_pool(self, context, old_pool, pool):
agent = self.get_pool_agent(context, pool['id'])
if pool['status'] in ACTIVE_PENDING: if pool['status'] in ACTIVE_PENDING:
if pool['vip_id'] is not None: if pool['vip_id'] is not None:
self.agent_rpc.reload_pool(context, pool['id']) self.agent_rpc.reload_pool(context, pool['id'], agent['host'])
else: else:
self.agent_rpc.destroy_pool(context, pool['id']) self.agent_rpc.destroy_pool(context, pool['id'], agent['host'])
def delete_pool(self, context, pool): def delete_pool(self, context, pool):
agent = self.plugin.get_lbaas_agent_hosting_pool(context, pool['id'])
if agent:
self.agent_rpc.destroy_pool(context, pool['id'],
agent['agent']['host'])
self.plugin._delete_db_pool(context, pool['id']) self.plugin._delete_db_pool(context, pool['id'])
self.agent_rpc.destroy_pool(context, pool['id'])
def create_member(self, context, member): def create_member(self, context, member):
self.agent_rpc.modify_pool(context, member['pool_id']) agent = self.get_pool_agent(context, member['pool_id'])
self.agent_rpc.modify_pool(context, member['pool_id'], agent['host'])
def update_member(self, context, old_member, member): def update_member(self, context, old_member, member):
# member may change pool id # member may change pool id
if member['pool_id'] != old_member['pool_id']: if member['pool_id'] != old_member['pool_id']:
self.agent_rpc.modify_pool(context, old_member['pool_id']) agent = self.plugin.get_lbaas_agent_hosting_pool(
self.agent_rpc.modify_pool(context, member['pool_id']) context, old_member['pool_id'])
if agent:
self.agent_rpc.modify_pool(context,
old_member['pool_id'],
agent['agent']['host'])
agent = self.get_pool_agent(context, member['pool_id'])
self.agent_rpc.modify_pool(context, member['pool_id'], agent['host'])
def delete_member(self, context, member): def delete_member(self, context, member):
self.plugin._delete_db_member(context, member['id']) self.plugin._delete_db_member(context, member['id'])
self.agent_rpc.modify_pool(context, member['pool_id']) agent = self.get_pool_agent(context, member['pool_id'])
self.agent_rpc.modify_pool(context, member['pool_id'], agent['host'])
def update_health_monitor(self, context, old_health_monitor, def update_health_monitor(self, context, old_health_monitor,
health_monitor, pool_id): health_monitor, pool_id):
# monitors are unused here because agent will fetch what is necessary # monitors are unused here because agent will fetch what is necessary
self.agent_rpc.modify_pool(context, pool_id) agent = self.get_pool_agent(context, pool_id)
self.agent_rpc.modify_pool(context, pool_id, agent['host'])
def create_pool_health_monitor(self, context, healthmon, pool_id): def create_pool_health_monitor(self, context, healthmon, pool_id):
# healthmon is not used here # healthmon is not used here
self.agent_rpc.modify_pool(context, pool_id) agent = self.get_pool_agent(context, pool_id)
self.agent_rpc.modify_pool(context, pool_id, agent['host'])
def delete_pool_health_monitor(self, context, health_monitor, pool_id): def delete_pool_health_monitor(self, context, health_monitor, pool_id):
self.plugin._delete_db_pool_health_monitor( self.plugin._delete_db_pool_health_monitor(
@ -285,7 +351,8 @@ class HaproxyOnHostPluginDriver(abstract_driver.LoadBalancerAbstractDriver):
) )
# healthmon_id is not used here # healthmon_id is not used here
self.agent_rpc.modify_pool(context, pool_id) agent = self.get_pool_agent(context, pool_id)
self.agent_rpc.modify_pool(context, pool_id, agent['host'])
def create_health_monitor(self, context, health_monitor): def create_health_monitor(self, context, health_monitor):
pass pass

View File

@ -58,7 +58,7 @@ class NoopLbaaSDriver(abstract_driver.LoadBalancerAbstractDriver):
@log.log @log.log
def delete_pool(self, context, pool): def delete_pool(self, context, pool):
pass self.plugin._delete_db_pool(context, pool["id"])
@log.log @log.log
def stats(self, context, pool_id): def stats(self, context, pool_id):

View File

@ -23,6 +23,7 @@ from neutron.db.loadbalancer import loadbalancer_db
from neutron.openstack.common import importutils from neutron.openstack.common import importutils
from neutron.openstack.common import log as logging from neutron.openstack.common import log as logging
from neutron.plugins.common import constants from neutron.plugins.common import constants
from neutron.services.loadbalancer import agent_scheduler
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -39,7 +40,8 @@ cfg.CONF.register_opts(lbaas_plugin_opts, "LBAAS")
legacy.override_config(cfg.CONF, [('LBAAS', 'driver_fqn')]) legacy.override_config(cfg.CONF, [('LBAAS', 'driver_fqn')])
class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb): class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
agent_scheduler.LbaasAgentSchedulerDbMixin):
"""Implementation of the Neutron Loadbalancer Service Plugin. """Implementation of the Neutron Loadbalancer Service Plugin.
@ -47,7 +49,12 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb):
Most DB related works are implemented in class Most DB related works are implemented in class
loadbalancer_db.LoadBalancerPluginDb. loadbalancer_db.LoadBalancerPluginDb.
""" """
supported_extension_aliases = ["lbaas"] supported_extension_aliases = ["lbaas", "lbaas_agent_scheduler"]
# lbaas agent notifiers to handle agent update operations;
# can be updated by plugin drivers while loading;
# will be extracted by neutron manager when loading service plugins;
agent_notifiers = {}
def __init__(self): def __init__(self):
"""Initialization for the loadbalancer service plugin.""" """Initialization for the loadbalancer service plugin."""
@ -213,7 +220,7 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb):
# update the db and return the value from db # update the db and return the value from db
# else - return what we have in db # else - return what we have in db
if stats_data: if stats_data:
super(LoadBalancerPlugin, self)._update_pool_stats( super(LoadBalancerPlugin, self).update_pool_stats(
context, context,
pool_id, pool_id,
stats_data stats_data

View File

@ -16,14 +16,17 @@
import contextlib import contextlib
import logging import logging
import os import os
import testtools
import mock
from oslo.config import cfg
import testtools
import webob.exc import webob.exc
from neutron.api.extensions import ExtensionMiddleware from neutron.api.extensions import ExtensionMiddleware
from neutron.api.extensions import PluginAwareExtensionManager from neutron.api.extensions import PluginAwareExtensionManager
from neutron.common import config from neutron.common import config
from neutron import context from neutron import context
import neutron.db.l3_db # noqa
from neutron.db.loadbalancer import loadbalancer_db as ldb from neutron.db.loadbalancer import loadbalancer_db as ldb
import neutron.extensions import neutron.extensions
from neutron.extensions import loadbalancer from neutron.extensions import loadbalancer
@ -46,34 +49,19 @@ ETCDIR = os.path.join(ROOTDIR, 'etc')
extensions_path = ':'.join(neutron.extensions.__path__) extensions_path = ':'.join(neutron.extensions.__path__)
_subnet_id = "0c798ed8-33ba-11e2-8b28-000c291c4d14"
def etcdir(*p): def etcdir(*p):
return os.path.join(ETCDIR, *p) return os.path.join(ETCDIR, *p)
class LoadBalancerPluginDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase): class LoadBalancerTestMixin(object):
resource_prefix_map = dict( resource_prefix_map = dict(
(k, constants.COMMON_PREFIXES[constants.LOADBALANCER]) (k, constants.COMMON_PREFIXES[constants.LOADBALANCER])
for k in loadbalancer.RESOURCE_ATTRIBUTE_MAP.keys() for k in loadbalancer.RESOURCE_ATTRIBUTE_MAP.keys()
) )
def setUp(self, core_plugin=None, lb_plugin=None):
service_plugins = {'lb_plugin_name': DB_LB_PLUGIN_KLASS}
super(LoadBalancerPluginDbTestCase, self).setUp(
service_plugins=service_plugins
)
self._subnet_id = "0c798ed8-33ba-11e2-8b28-000c291c4d14"
self.plugin = loadbalancer_plugin.LoadBalancerPlugin()
ext_mgr = PluginAwareExtensionManager(
extensions_path,
{constants.LOADBALANCER: self.plugin}
)
app = config.load_paste_app('extensions_test_app')
self.ext_api = ExtensionMiddleware(app, ext_mgr=ext_mgr)
def _create_vip(self, fmt, name, pool_id, protocol, protocol_port, def _create_vip(self, fmt, name, pool_id, protocol, protocol_port,
admin_state_up, expected_res_status=None, **kwargs): admin_state_up, expected_res_status=None, **kwargs):
data = {'vip': {'name': name, data = {'vip': {'name': name,
@ -97,7 +85,7 @@ class LoadBalancerPluginDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
def _create_pool(self, fmt, name, lb_method, protocol, admin_state_up, def _create_pool(self, fmt, name, lb_method, protocol, admin_state_up,
expected_res_status=None, **kwargs): expected_res_status=None, **kwargs):
data = {'pool': {'name': name, data = {'pool': {'name': name,
'subnet_id': self._subnet_id, 'subnet_id': _subnet_id,
'lb_method': lb_method, 'lb_method': lb_method,
'protocol': protocol, 'protocol': protocol,
'admin_state_up': admin_state_up, 'admin_state_up': admin_state_up,
@ -151,12 +139,6 @@ class LoadBalancerPluginDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
return res return res
def _api_for_resource(self, resource):
if resource in ['networks', 'subnets', 'ports']:
return self.api
else:
return self.ext_api
@contextlib.contextmanager @contextlib.contextmanager
def vip(self, fmt=None, name='vip1', pool=None, subnet=None, def vip(self, fmt=None, name='vip1', pool=None, subnet=None,
protocol='HTTP', protocol_port=80, admin_state_up=True, protocol='HTTP', protocol_port=80, admin_state_up=True,
@ -270,7 +252,43 @@ class LoadBalancerPluginDbTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
self._delete('health_monitors', the_health_monitor['id']) self._delete('health_monitors', the_health_monitor['id'])
class LoadBalancerPluginDbTestCase(LoadBalancerTestMixin,
test_db_plugin.NeutronDbPluginV2TestCase):
def setUp(self, core_plugin=None, lb_plugin=None):
service_plugins = {'lb_plugin_name': DB_LB_PLUGIN_KLASS}
super(LoadBalancerPluginDbTestCase, self).setUp(
service_plugins=service_plugins
)
self._subnet_id = _subnet_id
self.plugin = loadbalancer_plugin.LoadBalancerPlugin()
get_lbaas_agent_patcher = mock.patch(
'neutron.services.loadbalancer.agent_scheduler'
'.LbaasAgentSchedulerDbMixin.get_lbaas_agent_hosting_pool')
mock_lbaas_agent = mock.MagicMock()
get_lbaas_agent_patcher.start().return_value = mock_lbaas_agent
mock_lbaas_agent.__getitem__.return_value = {'host': 'host'}
self.addCleanup(mock.patch.stopall)
ext_mgr = PluginAwareExtensionManager(
extensions_path,
{constants.LOADBALANCER: self.plugin}
)
app = config.load_paste_app('extensions_test_app')
self.ext_api = ExtensionMiddleware(app, ext_mgr=ext_mgr)
class TestLoadBalancer(LoadBalancerPluginDbTestCase): class TestLoadBalancer(LoadBalancerPluginDbTestCase):
def setUp(self):
cfg.CONF.set_override('driver_fqn',
'neutron.services.loadbalancer.drivers.noop'
'.noop_driver.NoopLbaaSDriver',
group='LBAAS')
self.addCleanup(cfg.CONF.reset)
super(TestLoadBalancer, self).setUp()
def test_create_vip(self, **extras): def test_create_vip(self, **extras):
expected = { expected = {
'name': 'vip1', 'name': 'vip1',

View File

@ -95,6 +95,7 @@ class DummyServicePlugin(ServicePluginBase):
""" """
supported_extension_aliases = ['dummy', servicetype.EXT_ALIAS] supported_extension_aliases = ['dummy', servicetype.EXT_ALIAS]
agent_notifiers = {'dummy': 'dummy_agent_notifier'}
def __init__(self): def __init__(self):
self.svctype_mgr = servicetype_db.ServiceTypeManager.get_instance() self.svctype_mgr = servicetype_db.ServiceTypeManager.get_instance()

View File

@ -1106,7 +1106,8 @@ class OvsL3AgentNotifierTestCase(test_l3_plugin.L3NatTestCaseMixin,
def test_router_add_to_l3_agent_notification(self): def test_router_add_to_l3_agent_notification(self):
plugin = manager.NeutronManager.get_plugin() plugin = manager.NeutronManager.get_plugin()
with mock.patch.object(plugin.l3_agent_notifier, 'cast') as mock_l3: l3_notifier = plugin.agent_notifiers[constants.AGENT_TYPE_L3]
with mock.patch.object(l3_notifier, 'cast') as mock_l3:
with self.router() as router1: with self.router() as router1:
self._register_agent_states() self._register_agent_states()
hosta_id = self._get_agent_id(constants.AGENT_TYPE_L3, hosta_id = self._get_agent_id(constants.AGENT_TYPE_L3,
@ -1116,14 +1117,15 @@ class OvsL3AgentNotifierTestCase(test_l3_plugin.L3NatTestCaseMixin,
routers = [router1['router']['id']] routers = [router1['router']['id']]
mock_l3.assert_called_with( mock_l3.assert_called_with(
mock.ANY, mock.ANY,
plugin.l3_agent_notifier.make_msg( l3_notifier.make_msg(
'router_added_to_agent', 'router_added_to_agent',
payload=routers), payload=routers),
topic='l3_agent.hosta') topic='l3_agent.hosta')
def test_router_remove_from_l3_agent_notification(self): def test_router_remove_from_l3_agent_notification(self):
plugin = manager.NeutronManager.get_plugin() plugin = manager.NeutronManager.get_plugin()
with mock.patch.object(plugin.l3_agent_notifier, 'cast') as mock_l3: l3_notifier = plugin.agent_notifiers[constants.AGENT_TYPE_L3]
with mock.patch.object(l3_notifier, 'cast') as mock_l3:
with self.router() as router1: with self.router() as router1:
self._register_agent_states() self._register_agent_states()
hosta_id = self._get_agent_id(constants.AGENT_TYPE_L3, hosta_id = self._get_agent_id(constants.AGENT_TYPE_L3,
@ -1133,22 +1135,22 @@ class OvsL3AgentNotifierTestCase(test_l3_plugin.L3NatTestCaseMixin,
self._remove_router_from_l3_agent(hosta_id, self._remove_router_from_l3_agent(hosta_id,
router1['router']['id']) router1['router']['id'])
mock_l3.assert_called_with( mock_l3.assert_called_with(
mock.ANY, plugin.l3_agent_notifier.make_msg( mock.ANY, l3_notifier.make_msg(
'router_removed_from_agent', 'router_removed_from_agent',
payload={'router_id': router1['router']['id']}), payload={'router_id': router1['router']['id']}),
topic='l3_agent.hosta') topic='l3_agent.hosta')
def test_agent_updated_l3_agent_notification(self): def test_agent_updated_l3_agent_notification(self):
plugin = manager.NeutronManager.get_plugin() plugin = manager.NeutronManager.get_plugin()
with mock.patch.object(plugin.l3_agent_notifier, 'cast') as mock_l3: l3_notifier = plugin.agent_notifiers[constants.AGENT_TYPE_L3]
with mock.patch.object(l3_notifier, 'cast') as mock_l3:
self._register_agent_states() self._register_agent_states()
hosta_id = self._get_agent_id(constants.AGENT_TYPE_L3, hosta_id = self._get_agent_id(constants.AGENT_TYPE_L3,
L3_HOSTA) L3_HOSTA)
self._disable_agent(hosta_id, admin_state_up=False) self._disable_agent(hosta_id, admin_state_up=False)
mock_l3.assert_called_with( mock_l3.assert_called_with(
mock.ANY, plugin.l3_agent_notifier.make_msg( mock.ANY, l3_notifier.make_msg(
'agent_updated', 'agent_updated', payload={'admin_state_up': False}),
payload={'admin_state_up': False}),
topic='l3_agent.hosta') topic='l3_agent.hosta')

View File

@ -53,13 +53,30 @@ class TestLoadBalancerCallbacks(TestLoadBalancerPluginBase):
self.callbacks = plugin_driver.LoadBalancerCallbacks( self.callbacks = plugin_driver.LoadBalancerCallbacks(
self.plugin_instance self.plugin_instance
) )
get_lbaas_agents_patcher = mock.patch(
'neutron.services.loadbalancer.agent_scheduler'
'.LbaasAgentSchedulerDbMixin.get_lbaas_agents')
get_lbaas_agents_patcher.start()
# mocking plugin_driver create_pool() as it does nothing more than
# pool scheduling which is beyond the scope of this test case
mock.patch('neutron.services.loadbalancer.drivers.haproxy'
'.plugin_driver.HaproxyOnHostPluginDriver'
'.create_pool').start()
self.addCleanup(mock.patch.stopall)
def test_get_ready_devices(self): def test_get_ready_devices(self):
with self.vip() as vip: with self.vip() as vip:
ready = self.callbacks.get_ready_devices( with mock.patch('neutron.services.loadbalancer.agent_scheduler'
context.get_admin_context(), '.LbaasAgentSchedulerDbMixin.'
) 'list_pools_on_lbaas_agent') as mock_agent_pools:
self.assertEqual(ready, [vip['vip']['pool_id']]) mock_agent_pools.return_value = {
'pools': [{'id': vip['vip']['pool_id']}]}
ready = self.callbacks.get_ready_devices(
context.get_admin_context(),
)
self.assertEqual(ready, [vip['vip']['pool_id']])
def test_get_ready_devices_multiple_vips_and_pools(self): def test_get_ready_devices_multiple_vips_and_pools(self):
ctx = context.get_admin_context() ctx = context.get_admin_context()
@ -100,11 +117,17 @@ class TestLoadBalancerCallbacks(TestLoadBalancerPluginBase):
self.assertEqual(ctx.session.query(ldb.Pool).count(), 3) self.assertEqual(ctx.session.query(ldb.Pool).count(), 3)
self.assertEqual(ctx.session.query(ldb.Vip).count(), 2) self.assertEqual(ctx.session.query(ldb.Vip).count(), 2)
ready = self.callbacks.get_ready_devices(ctx) with mock.patch('neutron.services.loadbalancer.agent_scheduler'
self.assertEqual(len(ready), 2) '.LbaasAgentSchedulerDbMixin'
self.assertIn(pools[0].id, ready) '.list_pools_on_lbaas_agent') as mock_agent_pools:
self.assertIn(pools[1].id, ready) mock_agent_pools.return_value = {'pools': [{'id': pools[0].id},
self.assertNotIn(pools[2].id, ready) {'id': pools[1].id},
{'id': pools[2].id}]}
ready = self.callbacks.get_ready_devices(ctx)
self.assertEqual(len(ready), 2)
self.assertIn(pools[0].id, ready)
self.assertIn(pools[1].id, ready)
self.assertNotIn(pools[2].id, ready)
# cleanup # cleanup
ctx.session.query(ldb.Pool).delete() ctx.session.query(ldb.Pool).delete()
ctx.session.query(ldb.Vip).delete() ctx.session.query(ldb.Vip).delete()
@ -119,11 +142,15 @@ class TestLoadBalancerCallbacks(TestLoadBalancerPluginBase):
vip['vip']['id'], vip['vip']['id'],
{'vip': {'status': constants.INACTIVE}} {'vip': {'status': constants.INACTIVE}}
) )
with mock.patch('neutron.services.loadbalancer.agent_scheduler'
ready = self.callbacks.get_ready_devices( '.LbaasAgentSchedulerDbMixin.'
context.get_admin_context(), 'list_pools_on_lbaas_agent') as mock_agent_pools:
) mock_agent_pools.return_value = {
self.assertFalse(ready) 'pools': [{'id': vip['vip']['pool_id']}]}
ready = self.callbacks.get_ready_devices(
context.get_admin_context(),
)
self.assertFalse(ready)
def test_get_ready_devices_inactive_pool(self): def test_get_ready_devices_inactive_pool(self):
with self.vip() as vip: with self.vip() as vip:
@ -135,11 +162,15 @@ class TestLoadBalancerCallbacks(TestLoadBalancerPluginBase):
vip['vip']['pool_id'], vip['vip']['pool_id'],
{'pool': {'status': constants.INACTIVE}} {'pool': {'status': constants.INACTIVE}}
) )
with mock.patch('neutron.services.loadbalancer.agent_scheduler'
ready = self.callbacks.get_ready_devices( '.LbaasAgentSchedulerDbMixin.'
context.get_admin_context(), 'list_pools_on_lbaas_agent') as mock_agent_pools:
) mock_agent_pools.return_value = {
self.assertFalse(ready) 'pools': [{'id': vip['vip']['pool_id']}]}
ready = self.callbacks.get_ready_devices(
context.get_admin_context(),
)
self.assertFalse(ready)
def test_get_logical_device_inactive(self): def test_get_logical_device_inactive(self):
with self.pool() as pool: with self.pool() as pool:
@ -235,26 +266,26 @@ class TestLoadBalancerAgentApi(base.BaseTestCase):
super(TestLoadBalancerAgentApi, self).setUp() super(TestLoadBalancerAgentApi, self).setUp()
self.addCleanup(mock.patch.stopall) self.addCleanup(mock.patch.stopall)
self.api = plugin_driver.LoadBalancerAgentApi('topic', 'host') self.api = plugin_driver.LoadBalancerAgentApi('topic')
self.mock_cast = mock.patch.object(self.api, 'cast').start() self.mock_cast = mock.patch.object(self.api, 'cast').start()
self.mock_msg = mock.patch.object(self.api, 'make_msg').start() self.mock_msg = mock.patch.object(self.api, 'make_msg').start()
def test_init(self): def test_init(self):
self.assertEqual(self.api.topic, 'topic') self.assertEqual(self.api.topic, 'topic')
self.assertEqual(self.api.host, 'host')
def _call_test_helper(self, method_name): def _call_test_helper(self, method_name):
rv = getattr(self.api, method_name)(mock.sentinel.context, 'the_id') rv = getattr(self.api, method_name)(mock.sentinel.context, 'test',
'host')
self.assertEqual(rv, self.mock_cast.return_value) self.assertEqual(rv, self.mock_cast.return_value)
self.mock_cast.assert_called_once_with( self.mock_cast.assert_called_once_with(
mock.sentinel.context, mock.sentinel.context,
self.mock_msg.return_value, self.mock_msg.return_value,
topic='topic' topic='topic.host'
) )
self.mock_msg.assert_called_once_with( self.mock_msg.assert_called_once_with(
method_name, method_name,
pool_id='the_id', pool_id='test',
host='host' host='host'
) )
@ -267,6 +298,21 @@ class TestLoadBalancerAgentApi(base.BaseTestCase):
def test_modify_pool(self): def test_modify_pool(self):
self._call_test_helper('modify_pool') self._call_test_helper('modify_pool')
def test_agent_updated(self):
rv = self.api.agent_updated(mock.sentinel.context, True, 'host')
self.assertEqual(rv, self.mock_cast.return_value)
self.mock_cast.assert_called_once_with(
mock.sentinel.context,
self.mock_msg.return_value,
topic='topic.host',
version='1.1'
)
self.mock_msg.assert_called_once_with(
'agent_updated',
payload={'admin_state_up': True}
)
class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase): class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
def setUp(self): def setUp(self):
@ -276,6 +322,12 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
super(TestLoadBalancerPluginNotificationWrapper, self).setUp() super(TestLoadBalancerPluginNotificationWrapper, self).setUp()
self.mock_api = api_cls.return_value self.mock_api = api_cls.return_value
# mocking plugin_driver create_pool() as it does nothing more than
# pool scheduling which is beyond the scope of this test case
mock.patch('neutron.services.loadbalancer.drivers.haproxy'
'.plugin_driver.HaproxyOnHostPluginDriver'
'.create_pool').start()
self.addCleanup(mock.patch.stopall) self.addCleanup(mock.patch.stopall)
def test_create_vip(self): def test_create_vip(self):
@ -284,7 +336,8 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
with self.vip(pool=pool, subnet=subnet) as vip: with self.vip(pool=pool, subnet=subnet) as vip:
self.mock_api.reload_pool.assert_called_once_with( self.mock_api.reload_pool.assert_called_once_with(
mock.ANY, mock.ANY,
vip['vip']['pool_id'] vip['vip']['pool_id'],
'host'
) )
def test_update_vip(self): def test_update_vip(self):
@ -302,7 +355,8 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
self.mock_api.reload_pool.assert_called_once_with( self.mock_api.reload_pool.assert_called_once_with(
mock.ANY, mock.ANY,
vip['vip']['pool_id'] vip['vip']['pool_id'],
'host'
) )
self.assertEqual( self.assertEqual(
@ -319,7 +373,8 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
self.plugin_instance.delete_vip(ctx, vip['vip']['id']) self.plugin_instance.delete_vip(ctx, vip['vip']['id'])
self.mock_api.destroy_pool.assert_called_once_with( self.mock_api.destroy_pool.assert_called_once_with(
mock.ANY, mock.ANY,
vip['vip']['pool_id'] vip['vip']['pool_id'],
'host'
) )
def test_create_pool(self): def test_create_pool(self):
@ -334,7 +389,7 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
ctx = context.get_admin_context() ctx = context.get_admin_context()
self.plugin_instance.update_pool(ctx, pool['pool']['id'], pool) self.plugin_instance.update_pool(ctx, pool['pool']['id'], pool)
self.mock_api.destroy_pool.assert_called_once_with( self.mock_api.destroy_pool.assert_called_once_with(
mock.ANY, pool['pool']['id']) mock.ANY, pool['pool']['id'], 'host')
self.assertFalse(self.mock_api.reload_pool.called) self.assertFalse(self.mock_api.reload_pool.called)
self.assertFalse(self.mock_api.modify_pool.called) self.assertFalse(self.mock_api.modify_pool.called)
@ -352,7 +407,7 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
ctx = context.get_admin_context() ctx = context.get_admin_context()
self.plugin_instance.update_pool(ctx, pool['pool']['id'], pool) self.plugin_instance.update_pool(ctx, pool['pool']['id'], pool)
self.mock_api.reload_pool.assert_called_once_with( self.mock_api.reload_pool.assert_called_once_with(
mock.ANY, pool['pool']['id']) mock.ANY, pool['pool']['id'], 'host')
self.assertFalse(self.mock_api.destroy_pool.called) self.assertFalse(self.mock_api.destroy_pool.called)
self.assertFalse(self.mock_api.modify_pool.called) self.assertFalse(self.mock_api.modify_pool.called)
@ -363,14 +418,14 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
res = req.get_response(self.ext_api) res = req.get_response(self.ext_api)
self.assertEqual(res.status_int, 204) self.assertEqual(res.status_int, 204)
self.mock_api.destroy_pool.assert_called_once_with( self.mock_api.destroy_pool.assert_called_once_with(
mock.ANY, pool['pool']['id']) mock.ANY, pool['pool']['id'], 'host')
def test_create_member(self): def test_create_member(self):
with self.pool() as pool: with self.pool() as pool:
pool_id = pool['pool']['id'] pool_id = pool['pool']['id']
with self.member(pool_id=pool_id): with self.member(pool_id=pool_id):
self.mock_api.modify_pool.assert_called_once_with( self.mock_api.modify_pool.assert_called_once_with(
mock.ANY, pool_id) mock.ANY, pool_id, 'host')
def test_update_member(self): def test_update_member(self):
with self.pool() as pool: with self.pool() as pool:
@ -381,7 +436,7 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
self.plugin_instance.update_member( self.plugin_instance.update_member(
ctx, member['member']['id'], member) ctx, member['member']['id'], member)
self.mock_api.modify_pool.assert_called_once_with( self.mock_api.modify_pool.assert_called_once_with(
mock.ANY, pool_id) mock.ANY, pool_id, 'host')
def test_update_member_new_pool(self): def test_update_member_new_pool(self):
with self.pool() as pool1: with self.pool() as pool1:
@ -397,8 +452,8 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
member) member)
self.assertEqual(2, self.mock_api.modify_pool.call_count) self.assertEqual(2, self.mock_api.modify_pool.call_count)
self.mock_api.modify_pool.assert_has_calls( self.mock_api.modify_pool.assert_has_calls(
[mock.call(mock.ANY, pool1_id), [mock.call(mock.ANY, pool1_id, 'host'),
mock.call(mock.ANY, pool2_id)]) mock.call(mock.ANY, pool2_id, 'host')])
def test_delete_member(self): def test_delete_member(self):
with self.pool() as pool: with self.pool() as pool:
@ -411,7 +466,7 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
res = req.get_response(self.ext_api) res = req.get_response(self.ext_api)
self.assertEqual(res.status_int, 204) self.assertEqual(res.status_int, 204)
self.mock_api.modify_pool.assert_called_once_with( self.mock_api.modify_pool.assert_called_once_with(
mock.ANY, pool_id) mock.ANY, pool_id, 'host')
def test_create_pool_health_monitor(self): def test_create_pool_health_monitor(self):
with self.pool() as pool: with self.pool() as pool:
@ -422,7 +477,7 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
hm, hm,
pool_id) pool_id)
self.mock_api.modify_pool.assert_called_once_with( self.mock_api.modify_pool.assert_called_once_with(
mock.ANY, pool_id) mock.ANY, pool_id, 'host')
def test_delete_pool_health_monitor(self): def test_delete_pool_health_monitor(self):
with self.pool() as pool: with self.pool() as pool:
@ -436,7 +491,7 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
self.plugin_instance.delete_pool_health_monitor( self.plugin_instance.delete_pool_health_monitor(
ctx, hm['health_monitor']['id'], pool_id) ctx, hm['health_monitor']['id'], pool_id)
self.mock_api.modify_pool.assert_called_once_with( self.mock_api.modify_pool.assert_called_once_with(
mock.ANY, pool_id) mock.ANY, pool_id, 'host')
def test_update_health_monitor_associated_with_pool(self): def test_update_health_monitor_associated_with_pool(self):
with self.health_monitor(type='HTTP') as monitor: with self.health_monitor(type='HTTP') as monitor:
@ -457,7 +512,8 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
self.assertEqual(res.status_int, 201) self.assertEqual(res.status_int, 201)
self.mock_api.modify_pool.assert_called_once_with( self.mock_api.modify_pool.assert_called_once_with(
mock.ANY, mock.ANY,
pool['pool']['id'] pool['pool']['id'],
'host'
) )
self.mock_api.reset_mock() self.mock_api.reset_mock()
@ -471,5 +527,6 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
req.get_response(self.ext_api) req.get_response(self.ext_api)
self.mock_api.modify_pool.assert_called_once_with( self.mock_api.modify_pool.assert_called_once_with(
mock.ANY, mock.ANY,
pool['pool']['id'] pool['pool']['id'],
'host'
) )

View File

@ -0,0 +1,200 @@
# Copyright (c) 2013 OpenStack Foundation.
#
# 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 mock
from webob import exc
from neutron.api import extensions
from neutron.api.v2 import attributes
from neutron.common import constants
from neutron import context
from neutron.extensions import agent
from neutron.extensions import lbaas_agentscheduler
from neutron import manager
from neutron.plugins.common import constants as plugin_const
from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer
from neutron.tests.unit.openvswitch import test_agent_scheduler
from neutron.tests.unit import test_agent_ext_plugin
from neutron.tests.unit import test_db_plugin as test_plugin
from neutron.tests.unit import test_extensions
LBAAS_HOSTA = 'hosta'
class AgentSchedulerTestMixIn(test_agent_scheduler.AgentSchedulerTestMixIn):
def _list_pools_hosted_by_lbaas_agent(self, agent_id,
expected_code=exc.HTTPOk.code,
admin_context=True):
path = "/agents/%s/%s.%s" % (agent_id,
lbaas_agentscheduler.LOADBALANCER_POOLS,
self.fmt)
return self._request_list(path, expected_code=expected_code,
admin_context=admin_context)
def _get_lbaas_agent_hosting_pool(self, pool_id,
expected_code=exc.HTTPOk.code,
admin_context=True):
path = "/lb/pools/%s/%s.%s" % (pool_id,
lbaas_agentscheduler.LOADBALANCER_AGENT,
self.fmt)
return self._request_list(path, expected_code=expected_code,
admin_context=admin_context)
class LBaaSAgentSchedulerTestCase(test_agent_ext_plugin.AgentDBTestMixIn,
AgentSchedulerTestMixIn,
test_db_loadbalancer.LoadBalancerTestMixin,
test_plugin.NeutronDbPluginV2TestCase):
fmt = 'json'
plugin_str = ('neutron.plugins.openvswitch.'
'ovs_neutron_plugin.OVSNeutronPluginV2')
def setUp(self):
# Save the global RESOURCE_ATTRIBUTE_MAP
self.saved_attr_map = {}
for resource, attrs in attributes.RESOURCE_ATTRIBUTE_MAP.iteritems():
self.saved_attr_map[resource] = attrs.copy()
service_plugins = {
'lb_plugin_name': test_db_loadbalancer.DB_LB_PLUGIN_KLASS}
super(LBaaSAgentSchedulerTestCase, self).setUp(
self.plugin_str, service_plugins=service_plugins)
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
self.adminContext = context.get_admin_context()
# Add the resources to the global attribute map
# This is done here as the setup process won't
# initialize the main API router which extends
# the global attribute map
attributes.RESOURCE_ATTRIBUTE_MAP.update(
agent.RESOURCE_ATTRIBUTE_MAP)
self.addCleanup(self.restore_attribute_map)
def restore_attribute_map(self):
# Restore the original RESOURCE_ATTRIBUTE_MAP
attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map
def test_report_states(self):
self._register_agent_states(lbaas_agents=True)
agents = self._list_agents()
self.assertEqual(6, len(agents['agents']))
def test_pool_scheduling_on_pool_creation(self):
self._register_agent_states(lbaas_agents=True)
with self.pool() as pool:
lbaas_agent = self._get_lbaas_agent_hosting_pool(
pool['pool']['id'])
self.assertIsNotNone(lbaas_agent)
self.assertEqual(lbaas_agent['agent']['agent_type'],
constants.AGENT_TYPE_LOADBALANCER)
pools = self._list_pools_hosted_by_lbaas_agent(
lbaas_agent['agent']['id'])
self.assertEqual(1, len(pools['pools']))
self.assertEqual(pool['pool'], pools['pools'][0])
def test_schedule_poll_with_disabled_agent(self):
lbaas_hosta = {
'binary': 'neutron-loadbalancer-agent',
'host': LBAAS_HOSTA,
'topic': 'LOADBALANCER_AGENT',
'configurations': {'device_driver': 'device_driver',
'interface_driver': 'interface_driver'},
'agent_type': constants.AGENT_TYPE_LOADBALANCER}
self._register_one_agent_state(lbaas_hosta)
with self.pool() as pool:
lbaas_agent = self._get_lbaas_agent_hosting_pool(
pool['pool']['id'])
self.assertIsNotNone(lbaas_agent)
agents = self._list_agents()
self._disable_agent(agents['agents'][0]['id'])
pool = {'pool': {'name': 'test',
'subnet_id': 'test',
'lb_method': 'ROUND_ROBIN',
'protocol': 'HTTP',
'admin_state_up': True,
'tenant_id': 'test',
'description': 'test'}}
lbaas_plugin = manager.NeutronManager.get_service_plugins()[
plugin_const.LOADBALANCER]
self.assertRaises(lbaas_agentscheduler.NoEligibleLbaasAgent,
lbaas_plugin.create_pool, self.adminContext, pool)
def test_schedule_poll_with_down_agent(self):
lbaas_hosta = {
'binary': 'neutron-loadbalancer-agent',
'host': LBAAS_HOSTA,
'topic': 'LOADBALANCER_AGENT',
'configurations': {'device_driver': 'device_driver',
'interface_driver': 'interface_driver'},
'agent_type': constants.AGENT_TYPE_LOADBALANCER}
self._register_one_agent_state(lbaas_hosta)
is_agent_down_str = 'neutron.db.agents_db.AgentDbMixin.is_agent_down'
with mock.patch(is_agent_down_str) as mock_is_agent_down:
mock_is_agent_down.return_value = False
with self.pool() as pool:
lbaas_agent = self._get_lbaas_agent_hosting_pool(
pool['pool']['id'])
self.assertIsNotNone(lbaas_agent)
with mock.patch(is_agent_down_str) as mock_is_agent_down:
mock_is_agent_down.return_value = True
pool = {'pool': {'name': 'test',
'subnet_id': 'test',
'lb_method': 'ROUND_ROBIN',
'protocol': 'HTTP',
'admin_state_up': True,
'tenant_id': 'test',
'description': 'test'}}
lbaas_plugin = manager.NeutronManager.get_service_plugins()[
plugin_const.LOADBALANCER]
self.assertRaises(lbaas_agentscheduler.NoEligibleLbaasAgent,
lbaas_plugin.create_pool,
self.adminContext, pool)
def test_pool_unscheduling_on_pool_deletion(self):
self._register_agent_states(lbaas_agents=True)
with self.pool(no_delete=True) as pool:
lbaas_agent = self._get_lbaas_agent_hosting_pool(
pool['pool']['id'])
self.assertIsNotNone(lbaas_agent)
self.assertEqual(lbaas_agent['agent']['agent_type'],
constants.AGENT_TYPE_LOADBALANCER)
pools = self._list_pools_hosted_by_lbaas_agent(
lbaas_agent['agent']['id'])
self.assertEqual(1, len(pools['pools']))
self.assertEqual(pool['pool'], pools['pools'][0])
req = self.new_delete_request('pools',
pool['pool']['id'])
res = req.get_response(self.ext_api)
self.assertEqual(res.status_int, 204)
pools = self._list_pools_hosted_by_lbaas_agent(
lbaas_agent['agent']['id'])
self.assertEqual(0, len(pools['pools']))
def test_pool_scheduling_non_admin_access(self):
self._register_agent_states(lbaas_agents=True)
with self.pool() as pool:
self._get_lbaas_agent_hosting_pool(
pool['pool']['id'],
expected_code=exc.HTTPForbidden.code,
admin_context=False)
self._list_pools_hosted_by_lbaas_agent(
'fake_id',
expected_code=exc.HTTPForbidden.code,
admin_context=False)
class LBaaSAgentSchedulerTestCaseXML(LBaaSAgentSchedulerTestCase):
fmt = 'xml'

View File

@ -45,6 +45,8 @@ DHCP_HOSTA = 'hosta'
L3_HOSTB = 'hostb' L3_HOSTB = 'hostb'
DHCP_HOSTC = 'hostc' DHCP_HOSTC = 'hostc'
DHCP_HOST1 = 'host1' DHCP_HOST1 = 'host1'
LBAAS_HOSTA = 'hosta'
LBAAS_HOSTB = 'hostb'
class AgentTestExtensionManager(object): class AgentTestExtensionManager(object):
@ -83,7 +85,7 @@ class AgentDBTestMixIn(object):
self.assertEqual(agent_res.status_int, expected_res_status) self.assertEqual(agent_res.status_int, expected_res_status)
return agent_res return agent_res
def _register_agent_states(self): def _register_agent_states(self, lbaas_agents=False):
"""Register two L3 agents and two DHCP agents.""" """Register two L3 agents and two DHCP agents."""
l3_hosta = { l3_hosta = {
'binary': 'neutron-l3-agent', 'binary': 'neutron-l3-agent',
@ -110,6 +112,16 @@ class AgentDBTestMixIn(object):
'agent_type': constants.AGENT_TYPE_DHCP} 'agent_type': constants.AGENT_TYPE_DHCP}
dhcp_hostc = copy.deepcopy(dhcp_hosta) dhcp_hostc = copy.deepcopy(dhcp_hosta)
dhcp_hostc['host'] = DHCP_HOSTC dhcp_hostc['host'] = DHCP_HOSTC
lbaas_hosta = {
'binary': 'neutron-loadbalancer-agent',
'host': LBAAS_HOSTA,
'topic': 'LOADBALANCER_AGENT',
'configurations': {'device_driver': 'device_driver',
'interface_driver': 'interface_driver',
},
'agent_type': constants.AGENT_TYPE_LOADBALANCER}
lbaas_hostb = copy.deepcopy(lbaas_hosta)
lbaas_hostb['host'] = LBAAS_HOSTB
callback = agents_db.AgentExtRpcCallback() callback = agents_db.AgentExtRpcCallback()
callback.report_state(self.adminContext, callback.report_state(self.adminContext,
agent_state={'agent_state': l3_hosta}, agent_state={'agent_state': l3_hosta},
@ -123,7 +135,18 @@ class AgentDBTestMixIn(object):
callback.report_state(self.adminContext, callback.report_state(self.adminContext,
agent_state={'agent_state': dhcp_hostc}, agent_state={'agent_state': dhcp_hostc},
time=timeutils.strtime()) time=timeutils.strtime())
return [l3_hosta, l3_hostb, dhcp_hosta, dhcp_hostc]
res = [l3_hosta, l3_hostb, dhcp_hosta, dhcp_hostc]
if lbaas_agents:
callback.report_state(self.adminContext,
agent_state={'agent_state': lbaas_hosta},
time=timeutils.strtime())
callback.report_state(self.adminContext,
agent_state={'agent_state': lbaas_hostb},
time=timeutils.strtime())
res += [lbaas_hosta, lbaas_hostb]
return res
def _register_one_dhcp_agent(self): def _register_one_dhcp_agent(self):
"""Register one DHCP agent.""" """Register one DHCP agent."""

View File

@ -47,6 +47,11 @@ class MultiServiceCorePlugin(object):
supported_extension_aliases = ['lbaas', 'dummy'] supported_extension_aliases = ['lbaas', 'dummy']
class CorePluginWithAgentNotifiers(object):
agent_notifiers = {'l3': 'l3_agent_notifier',
'dhcp': 'dhcp_agent_notifier'}
class NeutronManagerTestCase(base.BaseTestCase): class NeutronManagerTestCase(base.BaseTestCase):
def setUp(self): def setUp(self):
@ -121,3 +126,16 @@ class NeutronManagerTestCase(base.BaseTestCase):
self.assertIsNotNone(validate_pre_plugin_load()) self.assertIsNotNone(validate_pre_plugin_load())
cfg.CONF.set_override('core_plugin', 'dummy.plugin') cfg.CONF.set_override('core_plugin', 'dummy.plugin')
self.assertIsNone(validate_pre_plugin_load()) self.assertIsNone(validate_pre_plugin_load())
def test_manager_gathers_agent_notifiers_from_service_plugins(self):
cfg.CONF.set_override("service_plugins",
["neutron.tests.unit.dummy_plugin."
"DummyServicePlugin"])
cfg.CONF.set_override("core_plugin",
"neutron.tests.unit.test_neutron_manager."
"CorePluginWithAgentNotifiers")
expected = {'l3': 'l3_agent_notifier',
'dhcp': 'dhcp_agent_notifier',
'dummy': 'dummy_agent_notifier'}
core_plugin = NeutronManager.get_plugin()
self.assertEqual(expected, core_plugin.agent_notifiers)