LBaaS integration with service type framework
The patch makes the following changes: * adds new attribute of the pool: provider, which is provider name as it is written in configuration * adds support for multiple plugin drivers for loadbalancer * cleans up healthmonitor-related plugin driver API Drivers should work with healthmonitor associations only * adds ability to update provider attribute for the pool used to reassociate pools with new providers in case their providers were removed from configuration implements blueprint lbaas-integration-with-service-types DocImpact Change-Id: I4295c9bcceb38e60f813d5596af48bd8194c1c9b
This commit is contained in:
parent
df514c0f50
commit
aa2ccf3991
@ -312,12 +312,6 @@ admin_user = %SERVICE_USER%
|
||||
admin_password = %SERVICE_PASSWORD%
|
||||
signing_dir = $state_path/keystone-signing
|
||||
|
||||
[lbaas]
|
||||
# ==================================================================================================
|
||||
# driver_fqn is the fully qualified name of the lbaas driver that will be loaded by the lbass plugin
|
||||
# ==================================================================================================
|
||||
# driver_fqn = neutron.services.loadbalancer.drivers.haproxy.plugin_driver.HaproxyOnHostPluginDriver
|
||||
|
||||
[database]
|
||||
# This line MUST be changed to actually run the plugin.
|
||||
# Example:
|
||||
@ -368,3 +362,5 @@ signing_dir = $state_path/keystone-signing
|
||||
# service_provider=LOADBALANCER:name:lbaas_plugin_driver_path:default
|
||||
# example of non-default provider:
|
||||
# service_provider=FIREWALL:name2:firewall_driver_path
|
||||
# --- Reference implementations ---
|
||||
service_provider=LOADBALANCER:Haproxy:neutron.services.loadbalancer.drivers.haproxy.plugin_driver.HaproxyOnHostPluginDriver:default
|
||||
|
@ -25,6 +25,7 @@ from neutron.common import exceptions as q_exc
|
||||
from neutron.db import db_base_plugin_v2 as base_db
|
||||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
from neutron.db import servicetype_db as st_db
|
||||
from neutron.extensions import loadbalancer
|
||||
from neutron.extensions.loadbalancer import LoadBalancerPluginBase
|
||||
from neutron import manager
|
||||
@ -130,6 +131,14 @@ class Pool(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant,
|
||||
cascade="all, delete-orphan")
|
||||
vip = orm.relationship(Vip, backref='pool')
|
||||
|
||||
provider = orm.relationship(
|
||||
st_db.ProviderResourceAssociation,
|
||||
uselist=False,
|
||||
lazy="joined",
|
||||
primaryjoin="Pool.id==ProviderResourceAssociation.resource_id",
|
||||
foreign_keys=[st_db.ProviderResourceAssociation.resource_id]
|
||||
)
|
||||
|
||||
|
||||
class HealthMonitor(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
|
||||
"""Represents a v2 neutron loadbalancer healthmonitor."""
|
||||
@ -457,7 +466,12 @@ class LoadBalancerPluginDb(LoadBalancerPluginBase,
|
||||
'lb_method': pool['lb_method'],
|
||||
'admin_state_up': pool['admin_state_up'],
|
||||
'status': pool['status'],
|
||||
'status_description': pool['status_description']}
|
||||
'status_description': pool['status_description'],
|
||||
'provider': ''
|
||||
}
|
||||
|
||||
if pool.provider:
|
||||
res['provider'] = pool.provider.provider_name
|
||||
|
||||
# Get the associated members
|
||||
res['members'] = [member['id'] for member in pool['members']]
|
||||
@ -465,7 +479,6 @@ class LoadBalancerPluginDb(LoadBalancerPluginBase,
|
||||
# Get the associated health_monitors
|
||||
res['health_monitors'] = [
|
||||
monitor['monitor_id'] for monitor in pool['monitors']]
|
||||
|
||||
return self._fields(res, fields)
|
||||
|
||||
def update_pool_stats(self, context, pool_id, data=None):
|
||||
@ -523,12 +536,10 @@ class LoadBalancerPluginDb(LoadBalancerPluginBase,
|
||||
pool_db.stats = self._create_pool_stats(context, pool_db['id'])
|
||||
context.session.add(pool_db)
|
||||
|
||||
pool_db = self._get_resource(context, Pool, pool_db['id'])
|
||||
return self._make_pool_dict(pool_db)
|
||||
|
||||
def update_pool(self, context, id, pool):
|
||||
p = pool['pool']
|
||||
|
||||
with context.session.begin(subtransactions=True):
|
||||
pool_db = self._get_resource(context, Pool, id)
|
||||
self.assert_modification_allowed(pool_db)
|
||||
|
@ -77,9 +77,10 @@ class ServiceTypeManager(object):
|
||||
def add_resource_association(self, context, service_type, provider_name,
|
||||
resource_id):
|
||||
r = self.conf.get_service_providers(
|
||||
filters={'service_type': service_type, 'name': provider_name})
|
||||
filters={'service_type': [service_type], 'name': [provider_name]})
|
||||
if not r:
|
||||
raise pconf.ServiceProviderNotFound(service_type=service_type)
|
||||
raise pconf.ServiceProviderNotFound(provider=provider_name,
|
||||
service_type=service_type)
|
||||
|
||||
with context.session.begin(subtransactions=True):
|
||||
# we don't actually need service type for association.
|
||||
@ -88,3 +89,12 @@ class ServiceTypeManager(object):
|
||||
assoc = ProviderResourceAssociation(provider_name=provider_name,
|
||||
resource_id=resource_id)
|
||||
context.session.add(assoc)
|
||||
|
||||
def del_resource_associations(self, context, resource_ids):
|
||||
if not resource_ids:
|
||||
return
|
||||
with context.session.begin(subtransactions=True):
|
||||
(context.session.query(ProviderResourceAssociation).
|
||||
filter(
|
||||
ProviderResourceAssociation.resource_id.in_(resource_ids)).
|
||||
delete(synchronize_session='fetch'))
|
||||
|
@ -162,6 +162,9 @@ RESOURCE_ATTRIBUTE_MAP = {
|
||||
'protocol': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:values': ['TCP', 'HTTP', 'HTTPS']},
|
||||
'is_visible': True},
|
||||
'provider': {'allow_post': True, 'allow_put': False,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True, 'default': attr.ATTR_NOT_SPECIFIED},
|
||||
'lb_method': {'allow_post': True, 'allow_put': True,
|
||||
'validate': {'type:string': None},
|
||||
'is_visible': True},
|
||||
|
@ -104,10 +104,6 @@ class LoadBalancerAbstractDriver(object):
|
||||
def delete_member(self, context, member):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_health_monitor(self, context, health_monitor):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_health_monitor(self, context,
|
||||
old_health_monitor,
|
||||
@ -115,13 +111,6 @@ class LoadBalancerAbstractDriver(object):
|
||||
pool_id):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_health_monitor(self, context, health_monitor):
|
||||
"""Driver may call the code below in order to delete the monitor.
|
||||
self.plugin._delete_db_health_monitor(context, health_monitor["id"])
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_pool_health_monitor(self, context,
|
||||
health_monitor,
|
||||
|
@ -354,11 +354,5 @@ class HaproxyOnHostPluginDriver(abstract_driver.LoadBalancerAbstractDriver):
|
||||
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):
|
||||
pass
|
||||
|
||||
def delete_health_monitor(self, context, health_monitor):
|
||||
self.plugin._delete_db_health_monitor(context, health_monitor["id"])
|
||||
|
||||
def stats(self, context, pool_id):
|
||||
pass
|
||||
|
@ -79,20 +79,12 @@ class NoopLbaaSDriver(abstract_driver.LoadBalancerAbstractDriver):
|
||||
def delete_member(self, context, member):
|
||||
self.plugin._delete_db_member(context, member["id"])
|
||||
|
||||
@log.log
|
||||
def create_health_monitor(self, context, health_monitor):
|
||||
pass
|
||||
|
||||
@log.log
|
||||
def update_health_monitor(self, context, old_health_monitor,
|
||||
health_monitor,
|
||||
pool_association):
|
||||
pass
|
||||
|
||||
@log.log
|
||||
def delete_health_monitor(self, context, health_monitor):
|
||||
self.plugin._delete_db_health_monitor(context, health_monitor["id"])
|
||||
|
||||
@log.log
|
||||
def create_pool_health_monitor(self, context,
|
||||
health_monitor, pool_id):
|
||||
|
@ -15,41 +15,32 @@
|
||||
#
|
||||
# @author: Avishay Balderman, Radware
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from neutron.common import legacy
|
||||
from neutron.api.v2 import attributes as attrs
|
||||
from neutron.common import exceptions as n_exc
|
||||
from neutron import context
|
||||
from neutron.db import api as qdbapi
|
||||
from neutron.db.loadbalancer import loadbalancer_db
|
||||
from neutron.openstack.common import importutils
|
||||
from neutron.db.loadbalancer import loadbalancer_db as ldb
|
||||
from neutron.db import servicetype_db as st_db
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.loadbalancer import agent_scheduler
|
||||
from neutron.services import provider_configuration as pconf
|
||||
from neutron.services import service_base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_DRIVER = ("neutron.services.loadbalancer.drivers.haproxy"
|
||||
".plugin_driver.HaproxyOnHostPluginDriver")
|
||||
|
||||
lbaas_plugin_opts = [
|
||||
cfg.StrOpt('driver_fqn',
|
||||
default=DEFAULT_DRIVER,
|
||||
help=_('LBaaS driver Fully Qualified Name'))
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(lbaas_plugin_opts, "LBAAS")
|
||||
legacy.override_config(cfg.CONF, [('LBAAS', 'driver_fqn')])
|
||||
|
||||
|
||||
class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
|
||||
class LoadBalancerPlugin(ldb.LoadBalancerPluginDb,
|
||||
agent_scheduler.LbaasAgentSchedulerDbMixin):
|
||||
|
||||
"""Implementation of the Neutron Loadbalancer Service Plugin.
|
||||
|
||||
This class manages the workflow of LBaaS request/response.
|
||||
Most DB related works are implemented in class
|
||||
loadbalancer_db.LoadBalancerPluginDb.
|
||||
"""
|
||||
supported_extension_aliases = ["lbaas", "lbaas_agent_scheduler"]
|
||||
supported_extension_aliases = ["lbaas",
|
||||
"lbaas_agent_scheduler",
|
||||
"service-type"]
|
||||
|
||||
# lbaas agent notifiers to handle agent update operations;
|
||||
# can be updated by plugin drivers while loading;
|
||||
@ -60,20 +51,51 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
|
||||
"""Initialization for the loadbalancer service plugin."""
|
||||
|
||||
qdbapi.register_models()
|
||||
self.service_type_manager = st_db.ServiceTypeManager.get_instance()
|
||||
self._load_drivers()
|
||||
|
||||
def _load_drivers(self):
|
||||
"""Loads plugin-driver from configuration.
|
||||
"""Loads plugin-drivers specified in configuration."""
|
||||
self.drivers, self.default_provider = service_base.load_drivers(
|
||||
constants.LOADBALANCER, self)
|
||||
|
||||
That method will later leverage service type framework
|
||||
# we're at the point when extensions are not loaded yet
|
||||
# so prevent policy from being loaded
|
||||
ctx = context.get_admin_context(load_admin_roles=False)
|
||||
# stop service in case provider was removed, but resources were not
|
||||
self._check_orphan_pool_associations(ctx, self.drivers.keys())
|
||||
|
||||
def _check_orphan_pool_associations(self, context, provider_names):
|
||||
"""Checks remaining associations between pools and providers.
|
||||
|
||||
If admin has not undeployed resources with provider that was deleted
|
||||
from configuration, neutron service is stopped. Admin must delete
|
||||
resources prior to removing providers from configuration.
|
||||
"""
|
||||
pools = self.get_pools(context)
|
||||
lost_providers = set([pool['provider'] for pool in pools
|
||||
if pool['provider'] not in provider_names])
|
||||
# resources are left without provider - stop the service
|
||||
if lost_providers:
|
||||
msg = _("Delete associated loadbalancer pools before "
|
||||
"removing providers %s") % list(lost_providers)
|
||||
LOG.exception(msg)
|
||||
raise SystemExit(msg)
|
||||
|
||||
def _get_driver_for_provider(self, provider):
|
||||
if provider in self.drivers:
|
||||
return self.drivers[provider]
|
||||
# raise if not associated (should never be reached)
|
||||
raise n_exc.Invalid(_("Error retrieving driver for provider %s") %
|
||||
provider)
|
||||
|
||||
def _get_driver_for_pool(self, context, pool_id):
|
||||
pool = self.get_pool(context, pool_id)
|
||||
try:
|
||||
self.driver = importutils.import_object(
|
||||
cfg.CONF.LBAAS.driver_fqn, self
|
||||
)
|
||||
except ImportError:
|
||||
LOG.exception(_("Error loading LBaaS driver %s"),
|
||||
cfg.CONF.LBAAS.driver_fqn)
|
||||
return self.drivers[pool['provider']]
|
||||
except KeyError:
|
||||
raise n_exc.Invalid(_("Error retrieving provider for pool %s") %
|
||||
pool_id)
|
||||
|
||||
def get_plugin_type(self):
|
||||
return constants.LOADBALANCER
|
||||
@ -83,7 +105,8 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
|
||||
|
||||
def create_vip(self, context, vip):
|
||||
v = super(LoadBalancerPlugin, self).create_vip(context, vip)
|
||||
self.driver.create_vip(context, v)
|
||||
driver = self._get_driver_for_pool(context, v['pool_id'])
|
||||
driver.create_vip(context, v)
|
||||
return v
|
||||
|
||||
def update_vip(self, context, id, vip):
|
||||
@ -91,7 +114,8 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
|
||||
vip['vip']['status'] = constants.PENDING_UPDATE
|
||||
old_vip = self.get_vip(context, id)
|
||||
v = super(LoadBalancerPlugin, self).update_vip(context, id, vip)
|
||||
self.driver.update_vip(context, old_vip, v)
|
||||
driver = self._get_driver_for_pool(context, v['pool_id'])
|
||||
driver.update_vip(context, old_vip, v)
|
||||
return v
|
||||
|
||||
def _delete_db_vip(self, context, id):
|
||||
@ -99,14 +123,37 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
|
||||
super(LoadBalancerPlugin, self).delete_vip(context, id)
|
||||
|
||||
def delete_vip(self, context, id):
|
||||
self.update_status(context, loadbalancer_db.Vip,
|
||||
self.update_status(context, ldb.Vip,
|
||||
id, constants.PENDING_DELETE)
|
||||
v = self.get_vip(context, id)
|
||||
self.driver.delete_vip(context, v)
|
||||
driver = self._get_driver_for_pool(context, v['pool_id'])
|
||||
driver.delete_vip(context, v)
|
||||
|
||||
def _get_provider_name(self, context, pool):
|
||||
if ('provider' in pool and
|
||||
pool['provider'] != attrs.ATTR_NOT_SPECIFIED):
|
||||
provider_name = pconf.normalize_provider_name(pool['provider'])
|
||||
self.validate_provider(provider_name)
|
||||
return provider_name
|
||||
else:
|
||||
if not self.default_provider:
|
||||
raise pconf.DefaultServiceProviderNotFound(
|
||||
service_type=constants.LOADBALANCER)
|
||||
return self.default_provider
|
||||
|
||||
def create_pool(self, context, pool):
|
||||
provider_name = self._get_provider_name(context, pool['pool'])
|
||||
p = super(LoadBalancerPlugin, self).create_pool(context, pool)
|
||||
self.driver.create_pool(context, p)
|
||||
|
||||
self.service_type_manager.add_resource_association(
|
||||
context,
|
||||
constants.LOADBALANCER,
|
||||
provider_name, p['id'])
|
||||
#need to add provider name to pool dict,
|
||||
#because provider was not known to db plugin at pool creation
|
||||
p['provider'] = provider_name
|
||||
driver = self.drivers[provider_name]
|
||||
driver.create_pool(context, p)
|
||||
return p
|
||||
|
||||
def update_pool(self, context, id, pool):
|
||||
@ -114,22 +161,28 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
|
||||
pool['pool']['status'] = constants.PENDING_UPDATE
|
||||
old_pool = self.get_pool(context, id)
|
||||
p = super(LoadBalancerPlugin, self).update_pool(context, id, pool)
|
||||
self.driver.update_pool(context, old_pool, p)
|
||||
driver = self._get_driver_for_provider(p['provider'])
|
||||
driver.update_pool(context, old_pool, p)
|
||||
return p
|
||||
|
||||
def _delete_db_pool(self, context, id):
|
||||
# proxy the call until plugin inherits from DBPlugin
|
||||
super(LoadBalancerPlugin, self).delete_pool(context, id)
|
||||
# rely on uuid uniqueness:
|
||||
with context.session.begin(subtransactions=True):
|
||||
self.service_type_manager.del_resource_associations(context, [id])
|
||||
super(LoadBalancerPlugin, self).delete_pool(context, id)
|
||||
|
||||
def delete_pool(self, context, id):
|
||||
self.update_status(context, loadbalancer_db.Pool,
|
||||
self.update_status(context, ldb.Pool,
|
||||
id, constants.PENDING_DELETE)
|
||||
p = self.get_pool(context, id)
|
||||
self.driver.delete_pool(context, p)
|
||||
driver = self._get_driver_for_provider(p['provider'])
|
||||
driver.delete_pool(context, p)
|
||||
|
||||
def create_member(self, context, member):
|
||||
m = super(LoadBalancerPlugin, self).create_member(context, member)
|
||||
self.driver.create_member(context, m)
|
||||
driver = self._get_driver_for_pool(context, m['pool_id'])
|
||||
driver.create_member(context, m)
|
||||
return m
|
||||
|
||||
def update_member(self, context, id, member):
|
||||
@ -137,7 +190,8 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
|
||||
member['member']['status'] = constants.PENDING_UPDATE
|
||||
old_member = self.get_member(context, id)
|
||||
m = super(LoadBalancerPlugin, self).update_member(context, id, member)
|
||||
self.driver.update_member(context, old_member, m)
|
||||
driver = self._get_driver_for_pool(context, m['pool_id'])
|
||||
driver.update_member(context, old_member, m)
|
||||
return m
|
||||
|
||||
def _delete_db_member(self, context, id):
|
||||
@ -145,17 +199,17 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
|
||||
super(LoadBalancerPlugin, self).delete_member(context, id)
|
||||
|
||||
def delete_member(self, context, id):
|
||||
self.update_status(context, loadbalancer_db.Member,
|
||||
self.update_status(context, ldb.Member,
|
||||
id, constants.PENDING_DELETE)
|
||||
m = self.get_member(context, id)
|
||||
self.driver.delete_member(context, m)
|
||||
driver = self._get_driver_for_pool(context, m['pool_id'])
|
||||
driver.delete_member(context, m)
|
||||
|
||||
def create_health_monitor(self, context, health_monitor):
|
||||
hm = super(LoadBalancerPlugin, self).create_health_monitor(
|
||||
context,
|
||||
health_monitor
|
||||
)
|
||||
self.driver.create_health_monitor(context, hm)
|
||||
return hm
|
||||
|
||||
def update_health_monitor(self, context, id, health_monitor):
|
||||
@ -168,11 +222,12 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
|
||||
|
||||
with context.session.begin(subtransactions=True):
|
||||
qry = context.session.query(
|
||||
loadbalancer_db.PoolMonitorAssociation
|
||||
).filter_by(monitor_id=hm['id'])
|
||||
ldb.PoolMonitorAssociation
|
||||
).filter_by(monitor_id=hm['id']).join(ldb.Pool)
|
||||
for assoc in qry:
|
||||
self.driver.update_health_monitor(context, old_hm,
|
||||
hm, assoc['pool_id'])
|
||||
driver = self._get_driver_for_pool(context, assoc['pool_id'])
|
||||
driver.update_health_monitor(context, old_hm,
|
||||
hm, assoc['pool_id'])
|
||||
return hm
|
||||
|
||||
def _delete_db_pool_health_monitor(self, context, hm_id, pool_id):
|
||||
@ -187,13 +242,14 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
|
||||
with context.session.begin(subtransactions=True):
|
||||
hm = self.get_health_monitor(context, id)
|
||||
qry = context.session.query(
|
||||
loadbalancer_db.PoolMonitorAssociation
|
||||
).filter_by(monitor_id=id)
|
||||
ldb.PoolMonitorAssociation
|
||||
).filter_by(monitor_id=id).join(ldb.Pool)
|
||||
for assoc in qry:
|
||||
self.driver.delete_pool_health_monitor(context,
|
||||
hm,
|
||||
assoc['pool_id'])
|
||||
self.driver.delete_health_monitor(context, hm)
|
||||
driver = self._get_driver_for_pool(context, assoc['pool_id'])
|
||||
driver.delete_pool_health_monitor(context,
|
||||
hm,
|
||||
assoc['pool_id'])
|
||||
super(LoadBalancerPlugin, self).delete_health_monitor(context, id)
|
||||
|
||||
def create_pool_health_monitor(self, context, health_monitor, pool_id):
|
||||
retval = super(LoadBalancerPlugin, self).create_pool_health_monitor(
|
||||
@ -203,19 +259,20 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
|
||||
)
|
||||
monitor_id = health_monitor['health_monitor']['id']
|
||||
hm = self.get_health_monitor(context, monitor_id)
|
||||
self.driver.create_pool_health_monitor(
|
||||
context, hm, pool_id)
|
||||
driver = self._get_driver_for_pool(context, pool_id)
|
||||
driver.create_pool_health_monitor(context, hm, pool_id)
|
||||
return retval
|
||||
|
||||
def delete_pool_health_monitor(self, context, id, pool_id):
|
||||
self.update_pool_health_monitor(context, id, pool_id,
|
||||
constants.PENDING_DELETE)
|
||||
hm = self.get_health_monitor(context, id)
|
||||
self.driver.delete_pool_health_monitor(
|
||||
context, hm, pool_id)
|
||||
driver = self._get_driver_for_pool(context, pool_id)
|
||||
driver.delete_pool_health_monitor(context, hm, pool_id)
|
||||
|
||||
def stats(self, context, pool_id):
|
||||
stats_data = self.driver.stats(context, pool_id)
|
||||
driver = self._get_driver_for_pool(context, pool_id)
|
||||
stats_data = driver.stats(context, pool_id)
|
||||
# if we get something from the driver -
|
||||
# update the db and return the value from db
|
||||
# else - return what we have in db
|
||||
@ -233,10 +290,13 @@ class LoadBalancerPlugin(loadbalancer_db.LoadBalancerPluginDb,
|
||||
|
||||
pool = self.get_pool(context, vip['pool_id'])
|
||||
vip['pool'] = pool
|
||||
vip['members'] = [
|
||||
self.get_member(context, member_id)
|
||||
for member_id in pool['members']]
|
||||
vip['health_monitors'] = [
|
||||
self.get_health_monitor(context, hm_id)
|
||||
for hm_id in pool['health_monitors']]
|
||||
vip['members'] = [self.get_member(context, member_id)
|
||||
for member_id in pool['members']]
|
||||
vip['health_monitors'] = [self.get_health_monitor(context, hm_id)
|
||||
for hm_id in pool['health_monitors']]
|
||||
return vip
|
||||
|
||||
def validate_provider(self, provider):
|
||||
if provider not in self.drivers:
|
||||
raise pconf.ServiceProviderNotFound(
|
||||
provider=provider, service_type=constants.LOADBALANCER)
|
||||
|
@ -42,8 +42,8 @@ def parse_service_provider_opt():
|
||||
"""Parse service definition opts and returns result."""
|
||||
def validate_name(name):
|
||||
if len(name) > 255:
|
||||
raise n_exc.Invalid("Provider name is limited by 255 characters:"
|
||||
" %s" % name)
|
||||
raise n_exc.Invalid(
|
||||
_("Provider name is limited by 255 characters: %s") % name)
|
||||
|
||||
svc_providers_opt = cfg.CONF.service_providers.service_provider
|
||||
res = []
|
||||
@ -79,16 +79,21 @@ def parse_service_provider_opt():
|
||||
return res
|
||||
|
||||
|
||||
class ServiceProviderNotFound(n_exc.NotFound):
|
||||
message = _("Service provider could not be found "
|
||||
class ServiceProviderNotFound(n_exc.InvalidInput):
|
||||
message = _("Service provider '%(provider)s' could not be found "
|
||||
"for service type %(service_type)s")
|
||||
|
||||
|
||||
class DefaultServiceProviderNotFound(ServiceProviderNotFound):
|
||||
class DefaultServiceProviderNotFound(n_exc.InvalidInput):
|
||||
message = _("Service type %(service_type)s does not have a default "
|
||||
"service provider")
|
||||
|
||||
|
||||
class ServiceProviderAlreadyAssociated(n_exc.Conflict):
|
||||
message = _("Resource '%(resource_id)s' is already associated with "
|
||||
"provider '%(provider)s' for service type '%(service_type)s'")
|
||||
|
||||
|
||||
class ProviderConfiguration(object):
|
||||
def __init__(self, prov_data):
|
||||
self.providers = {}
|
||||
|
@ -18,6 +18,12 @@
|
||||
import abc
|
||||
|
||||
from neutron.api import extensions
|
||||
from neutron.db import servicetype_db as sdb
|
||||
from neutron.openstack.common import importutils
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron.services import provider_configuration as pconf
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ServicePluginBase(extensions.PluginInterface):
|
||||
@ -46,3 +52,49 @@ class ServicePluginBase(extensions.PluginInterface):
|
||||
def get_plugin_description(self):
|
||||
"""Return string description of the plugin."""
|
||||
pass
|
||||
|
||||
|
||||
def load_drivers(service_type, plugin):
|
||||
"""Loads drivers for specific service.
|
||||
|
||||
Passes plugin instance to driver's constructor
|
||||
"""
|
||||
service_type_manager = sdb.ServiceTypeManager.get_instance()
|
||||
providers = (service_type_manager.
|
||||
get_service_providers(
|
||||
None,
|
||||
filters={'service_type': [service_type]})
|
||||
)
|
||||
if not providers:
|
||||
msg = (_("No providers specified for '%s' service, exiting") %
|
||||
service_type)
|
||||
LOG.error(msg)
|
||||
raise SystemExit(msg)
|
||||
|
||||
drivers = {}
|
||||
for provider in providers:
|
||||
try:
|
||||
drivers[provider['name']] = importutils.import_object(
|
||||
provider['driver'], plugin
|
||||
)
|
||||
LOG.debug(_("Loaded '%(provider)s' provider for service "
|
||||
"%(service_type)s"),
|
||||
{'provider': provider['driver'],
|
||||
'service_type': service_type})
|
||||
except ImportError:
|
||||
LOG.exception(_("Error loading provider '%(provider)s' for "
|
||||
"service %(service_type)s"),
|
||||
{'provider': provider['driver'],
|
||||
'service_type': service_type})
|
||||
raise
|
||||
|
||||
default_provider = None
|
||||
try:
|
||||
provider = service_type_manager.get_default_service_provider(
|
||||
None, service_type)
|
||||
default_provider = provider['name']
|
||||
except pconf.DefaultServiceProviderNotFound:
|
||||
LOG.info(_("Default provider is not specified for service type %s"),
|
||||
service_type)
|
||||
|
||||
return drivers, default_provider
|
||||
|
@ -28,12 +28,14 @@ from neutron.common import config
|
||||
from neutron import context
|
||||
import neutron.db.l3_db # noqa
|
||||
from neutron.db.loadbalancer import loadbalancer_db as ldb
|
||||
from neutron.db import servicetype_db as sdb
|
||||
import neutron.extensions
|
||||
from neutron.extensions import loadbalancer
|
||||
from neutron.plugins.common import constants
|
||||
from neutron.services.loadbalancer import (
|
||||
plugin as loadbalancer_plugin
|
||||
)
|
||||
from neutron.services import provider_configuration as pconf
|
||||
from neutron.tests.unit import test_db_plugin
|
||||
|
||||
|
||||
@ -90,10 +92,9 @@ class LoadBalancerTestMixin(object):
|
||||
'protocol': protocol,
|
||||
'admin_state_up': admin_state_up,
|
||||
'tenant_id': self._tenant_id}}
|
||||
for arg in ('description'):
|
||||
for arg in ('description', 'provider'):
|
||||
if arg in kwargs and kwargs[arg] is not None:
|
||||
data['pool'][arg] = kwargs[arg]
|
||||
|
||||
pool_req = self.new_create_request('pools', data, fmt)
|
||||
pool_res = pool_req.get_response(self.ext_api)
|
||||
if expected_res_status:
|
||||
@ -254,8 +255,19 @@ class LoadBalancerTestMixin(object):
|
||||
|
||||
class LoadBalancerPluginDbTestCase(LoadBalancerTestMixin,
|
||||
test_db_plugin.NeutronDbPluginV2TestCase):
|
||||
def setUp(self, core_plugin=None, lb_plugin=None):
|
||||
def setUp(self, core_plugin=None, lb_plugin=None, lbaas_provider=None):
|
||||
service_plugins = {'lb_plugin_name': DB_LB_PLUGIN_KLASS}
|
||||
if not lbaas_provider:
|
||||
lbaas_provider = (
|
||||
constants.LOADBALANCER +
|
||||
':lbaas:neutron.services.loadbalancer.'
|
||||
'drivers.noop.noop_driver.NoopLbaaSDriver:default')
|
||||
cfg.CONF.set_override('service_provider',
|
||||
[lbaas_provider],
|
||||
'service_providers')
|
||||
#force service type manager to reload configuration:
|
||||
sdb.ServiceTypeManager._instance = None
|
||||
|
||||
super(LoadBalancerPluginDbTestCase, self).setUp(
|
||||
service_plugins=service_plugins
|
||||
)
|
||||
@ -271,6 +283,7 @@ class LoadBalancerPluginDbTestCase(LoadBalancerTestMixin,
|
||||
get_lbaas_agent_patcher.start().return_value = mock_lbaas_agent
|
||||
mock_lbaas_agent.__getitem__.return_value = {'host': 'host'}
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
self.addCleanup(cfg.CONF.reset)
|
||||
|
||||
ext_mgr = PluginAwareExtensionManager(
|
||||
extensions_path,
|
||||
@ -282,10 +295,6 @@ class LoadBalancerPluginDbTestCase(LoadBalancerTestMixin,
|
||||
|
||||
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()
|
||||
|
||||
@ -566,6 +575,48 @@ class TestLoadBalancer(LoadBalancerPluginDbTestCase):
|
||||
pool = self.pool(name=name, lb_method='UNSUPPORTED')
|
||||
self.assertRaises(webob.exc.HTTPClientError, pool.__enter__)
|
||||
|
||||
def _create_pool_directly_via_plugin(self, provider_name):
|
||||
#default provider will be haproxy
|
||||
prov1 = (constants.LOADBALANCER +
|
||||
':lbaas:neutron.services.loadbalancer.'
|
||||
'drivers.noop.noop_driver.NoopLbaaSDriver')
|
||||
prov2 = (constants.LOADBALANCER +
|
||||
':haproxy:neutron.services.loadbalancer.'
|
||||
'drivers.haproxy.plugin_driver.HaproxyOnHostPluginDriver'
|
||||
':default')
|
||||
cfg.CONF.set_override('service_provider',
|
||||
[prov1, prov2],
|
||||
'service_providers')
|
||||
sdb.ServiceTypeManager._instance = None
|
||||
self.plugin = loadbalancer_plugin.LoadBalancerPlugin()
|
||||
with self.subnet() as subnet:
|
||||
ctx = context.get_admin_context()
|
||||
#create pool with another provider - lbaas
|
||||
#which is noop driver
|
||||
pool = {'name': 'pool1',
|
||||
'subnet_id': subnet['subnet']['id'],
|
||||
'lb_method': 'ROUND_ROBIN',
|
||||
'protocol': 'HTTP',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': self._tenant_id,
|
||||
'provider': provider_name,
|
||||
'description': ''}
|
||||
self.plugin.create_pool(ctx, {'pool': pool})
|
||||
assoc = ctx.session.query(sdb.ProviderResourceAssociation).one()
|
||||
self.assertEqual(assoc.provider_name,
|
||||
pconf.normalize_provider_name(provider_name))
|
||||
|
||||
def test_create_pool_another_provider(self):
|
||||
self._create_pool_directly_via_plugin('lbaas')
|
||||
|
||||
def test_create_pool_unnormalized_provider_name(self):
|
||||
self._create_pool_directly_via_plugin('LBAAS')
|
||||
|
||||
def test_create_pool_unexisting_provider(self):
|
||||
self.assertRaises(
|
||||
pconf.ServiceProviderNotFound,
|
||||
self._create_pool_directly_via_plugin, 'unexisting')
|
||||
|
||||
def test_create_pool(self):
|
||||
name = "pool1"
|
||||
keys = [('name', name),
|
||||
@ -1211,7 +1262,7 @@ class TestLoadBalancer(LoadBalancerPluginDbTestCase):
|
||||
res)
|
||||
|
||||
def test_driver_call_create_pool_health_monitor(self):
|
||||
with mock.patch.object(self.plugin.driver,
|
||||
with mock.patch.object(self.plugin.drivers['lbaas'],
|
||||
'create_pool_health_monitor') as driver_call:
|
||||
with contextlib.nested(
|
||||
self.pool(),
|
||||
@ -1342,6 +1393,30 @@ class TestLoadBalancer(LoadBalancerPluginDbTestCase):
|
||||
self.assertEqual(assoc['status'], 'ACTIVE')
|
||||
self.assertEqual(assoc['status_description'], 'ok')
|
||||
|
||||
def test_check_orphan_pool_associations(self):
|
||||
with contextlib.nested(
|
||||
#creating pools with default noop driver
|
||||
self.pool(),
|
||||
self.pool()
|
||||
) as (p1, p2):
|
||||
#checking that 3 associations exist
|
||||
ctx = context.get_admin_context()
|
||||
qry = ctx.session.query(sdb.ProviderResourceAssociation)
|
||||
self.assertEqual(qry.count(), 2)
|
||||
#removing driver
|
||||
cfg.CONF.set_override('service_provider',
|
||||
[constants.LOADBALANCER +
|
||||
':lbaas1:neutron.services.loadbalancer.'
|
||||
'drivers.noop.noop_driver.'
|
||||
'NoopLbaaSDriver:default'],
|
||||
'service_providers')
|
||||
sdb.ServiceTypeManager._instance = None
|
||||
# calling _remove_orphan... in constructor
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
loadbalancer_plugin.LoadBalancerPlugin
|
||||
)
|
||||
|
||||
|
||||
class TestLoadBalancerXML(TestLoadBalancer):
|
||||
fmt = 'xml'
|
||||
|
@ -21,6 +21,7 @@ import mock
|
||||
from neutron.common import exceptions
|
||||
from neutron import context
|
||||
from neutron.db.loadbalancer import loadbalancer_db as ldb
|
||||
from neutron.db import servicetype_db as st_db
|
||||
from neutron import manager
|
||||
from neutron.openstack.common import uuidutils
|
||||
from neutron.plugins.common import constants
|
||||
@ -35,8 +36,12 @@ class TestLoadBalancerPluginBase(
|
||||
test_db_loadbalancer.LoadBalancerPluginDbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestLoadBalancerPluginBase, self).setUp()
|
||||
|
||||
# needed to reload provider configuration
|
||||
st_db.ServiceTypeManager._instance = None
|
||||
super(TestLoadBalancerPluginBase, self).setUp(
|
||||
lbaas_provider=('LOADBALANCER:lbaas:neutron.services.'
|
||||
'loadbalancer.drivers.haproxy.plugin_driver.'
|
||||
'HaproxyOnHostPluginDriver:default'))
|
||||
# create another API instance to make testing easier
|
||||
# pass a mock to our API instance
|
||||
|
||||
@ -328,6 +333,13 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
|
||||
'.plugin_driver.HaproxyOnHostPluginDriver'
|
||||
'.create_pool').start()
|
||||
|
||||
self.mock_get_driver = mock.patch.object(self.plugin_instance,
|
||||
'_get_driver')
|
||||
self.mock_get_driver.return_value = (plugin_driver.
|
||||
HaproxyOnHostPluginDriver(
|
||||
self.plugin_instance
|
||||
))
|
||||
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
|
||||
def test_create_vip(self):
|
||||
@ -387,6 +399,7 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
|
||||
with self.pool() as pool:
|
||||
pool['pool']['status'] = 'INACTIVE'
|
||||
ctx = context.get_admin_context()
|
||||
del pool['pool']['provider']
|
||||
self.plugin_instance.update_pool(ctx, pool['pool']['id'], pool)
|
||||
self.mock_api.destroy_pool.assert_called_once_with(
|
||||
mock.ANY, pool['pool']['id'], 'host')
|
||||
@ -396,6 +409,7 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
|
||||
def test_update_pool_no_vip_id(self):
|
||||
with self.pool() as pool:
|
||||
ctx = context.get_admin_context()
|
||||
del pool['pool']['provider']
|
||||
self.plugin_instance.update_pool(ctx, pool['pool']['id'], pool)
|
||||
self.assertFalse(self.mock_api.destroy_pool.called)
|
||||
self.assertFalse(self.mock_api.reload_pool.called)
|
||||
@ -405,6 +419,7 @@ class TestLoadBalancerPluginNotificationWrapper(TestLoadBalancerPluginBase):
|
||||
with self.pool() as pool:
|
||||
with self.vip(pool=pool):
|
||||
ctx = context.get_admin_context()
|
||||
del pool['pool']['provider']
|
||||
self.plugin_instance.update_pool(ctx, pool['pool']['id'], pool)
|
||||
self.mock_api.reload_pool.assert_called_once_with(
|
||||
mock.ANY, pool['pool']['id'], 'host')
|
||||
|
@ -14,6 +14,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
import mock
|
||||
from oslo.config import cfg
|
||||
from webob import exc
|
||||
|
||||
from neutron.api import extensions
|
||||
@ -68,6 +69,15 @@ class LBaaSAgentSchedulerTestCase(test_agent_ext_plugin.AgentDBTestMixIn,
|
||||
self.saved_attr_map[resource] = attrs.copy()
|
||||
service_plugins = {
|
||||
'lb_plugin_name': test_db_loadbalancer.DB_LB_PLUGIN_KLASS}
|
||||
|
||||
#default provider should support agent scheduling
|
||||
cfg.CONF.set_override(
|
||||
'service_provider',
|
||||
[('LOADBALANCER:lbaas:neutron.services.'
|
||||
'loadbalancer.drivers.haproxy.plugin_driver.'
|
||||
'HaproxyOnHostPluginDriver:default')],
|
||||
'service_providers')
|
||||
|
||||
super(LBaaSAgentSchedulerTestCase, self).setUp(
|
||||
self.plugin_str, service_plugins=service_plugins)
|
||||
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
|
||||
@ -131,7 +141,7 @@ class LBaaSAgentSchedulerTestCase(test_agent_ext_plugin.AgentDBTestMixIn,
|
||||
self.assertRaises(lbaas_agentscheduler.NoEligibleLbaasAgent,
|
||||
lbaas_plugin.create_pool, self.adminContext, pool)
|
||||
|
||||
def test_schedule_poll_with_down_agent(self):
|
||||
def test_schedule_pool_with_down_agent(self):
|
||||
lbaas_hosta = {
|
||||
'binary': 'neutron-loadbalancer-agent',
|
||||
'host': LBAAS_HOSTA,
|
||||
@ -153,6 +163,7 @@ class LBaaSAgentSchedulerTestCase(test_agent_ext_plugin.AgentDBTestMixIn,
|
||||
'subnet_id': 'test',
|
||||
'lb_method': 'ROUND_ROBIN',
|
||||
'protocol': 'HTTP',
|
||||
'provider': 'lbaas',
|
||||
'admin_state_up': True,
|
||||
'tenant_id': 'test',
|
||||
'description': 'test'}}
|
||||
|
@ -23,7 +23,7 @@ from webob import exc
|
||||
import webtest
|
||||
|
||||
from neutron.api import extensions
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.api.v2 import attributes as attr
|
||||
from neutron.common import config
|
||||
from neutron.extensions import loadbalancer
|
||||
from neutron import manager
|
||||
@ -45,7 +45,7 @@ class LoadBalancerTestExtensionManager(object):
|
||||
# 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(
|
||||
attr.RESOURCE_ATTRIBUTE_MAP.update(
|
||||
loadbalancer.RESOURCE_ATTRIBUTE_MAP)
|
||||
return loadbalancer.Loadbalancer.get_resources()
|
||||
|
||||
@ -203,6 +203,7 @@ class LoadBalancerExtensionTestCase(testlib_api.WebTestCase):
|
||||
'admin_state_up': True,
|
||||
'tenant_id': _uuid()}}
|
||||
return_value = copy.copy(data['pool'])
|
||||
return_value['provider'] = 'lbaas'
|
||||
return_value.update({'status': "ACTIVE", 'id': pool_id})
|
||||
|
||||
instance = self.plugin.return_value
|
||||
@ -210,6 +211,7 @@ class LoadBalancerExtensionTestCase(testlib_api.WebTestCase):
|
||||
res = self.api.post(_get_path('lb/pools', fmt=self.fmt),
|
||||
self.serialize(data),
|
||||
content_type='application/%s' % self.fmt)
|
||||
data['pool']['provider'] = attr.ATTR_NOT_SPECIFIED
|
||||
instance.create_pool.assert_called_with(mock.ANY,
|
||||
pool=data)
|
||||
self.assertEqual(res.status_int, exc.HTTPCreated.code)
|
||||
|
Loading…
Reference in New Issue
Block a user