]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
LBaaS integration with service type framework
authorEugene Nikanorov <enikanorov@mirantis.com>
Sun, 7 Jul 2013 06:50:56 +0000 (10:50 +0400)
committerEugene Nikanorov <enikanorov@mirantis.com>
Tue, 3 Sep 2013 19:05:33 +0000 (23:05 +0400)
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

14 files changed:
etc/neutron.conf
neutron/db/loadbalancer/loadbalancer_db.py
neutron/db/servicetype_db.py
neutron/extensions/loadbalancer.py
neutron/services/loadbalancer/drivers/abstract_driver.py
neutron/services/loadbalancer/drivers/haproxy/plugin_driver.py
neutron/services/loadbalancer/drivers/noop/noop_driver.py
neutron/services/loadbalancer/plugin.py
neutron/services/provider_configuration.py
neutron/services/service_base.py
neutron/tests/unit/db/loadbalancer/test_db_loadbalancer.py
neutron/tests/unit/services/loadbalancer/drivers/haproxy/test_plugin_driver.py
neutron/tests/unit/services/loadbalancer/test_agent_scheduler.py
neutron/tests/unit/services/loadbalancer/test_loadbalancer_plugin.py

index d09e4254237198944fd809f368fe6238e46d3c18..5abf669175794d9340537f4401b0a9013f2c5a49 100644 (file)
@@ -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
index b7a759eb4129648aa16489f6b7acfd3329c889b3..d9d41cf5e46935c7719d6243f83df562d8038253 100644 (file)
@@ -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)
index 55f01d4894cb024cfa7c64b4aee8087060e2c6cb..d550c260ca704753e2ce39998f398742078d9463 100644 (file)
@@ -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'))
index 90050ee3a81cc0ea3c43a926bf14fe654fb118ca..67054fb962ae9b9036883302790a0b7ab195f2d2 100644 (file)
@@ -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},
index d053168521830c5fbe5055bc0032d559cf53a3fa..50212d1fc08a0f690b07bdc7bdd62f7ac5ba402c 100644 (file)
@@ -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,
index e9f2707cb33619d66f6cff9c16986d1e23ce556b..01a5f02a1c230495d761f8f5bb22bdc9a2abb40e 100644 (file)
@@ -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
index 01b65b6575d6bae0b01723f60d538daf10046f48..4060dca50f4797ff07ccd2fea68adc2c6043e606 100644 (file)
@@ -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):
index ad4914d7c503f414d7e57c2989baf6ff04c69456..ea7eff3aac6da321fd4563e8ca694febd2978f0a 100644 (file)
 #
 # @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.
-
-           That method will later leverage service type framework
+        """Loads plugin-drivers specified in configuration."""
+        self.drivers, self.default_provider = service_base.load_drivers(
+            constants.LOADBALANCER, self)
+
+        # 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)
index 757a03eac2a329afdc32e703cf76e8d4a69ceb35..85c2424d915eed2fc506ed1474b1c25493e75f3d 100644 (file)
@@ -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 = {}
index a4d113c82db609c69ec578dd63b3784f633e031e..a70c17f271c703a29827eac98202b28f4da5d3f3 100644 (file)
 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
index cecb85873ed2c8293ee6827735c0aac0c8728a09..483de34092253c381d64950e44dec8e86db1abfe 100644 (file)
@@ -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'
index 6cc0cc6c7beff8ab609ecfacd0ffed45dcd82530..1b1dd927b42523dd84a10702afdee0348c7bafae 100644 (file)
@@ -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')
index e3d3df0f8dc806753cf591bde1b6313193050a6c..330d4efcf9f750bc8d52f41954a3cf88d8bbd7bf 100644 (file)
@@ -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'}}
index a4130ba8fa3b24e8155f25849bb1717a08701392..0ed26cf4f4ad847667048e532345f63330ee89c7 100644 (file)
@@ -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)