]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
LBaaS new object model logging no-op driver
authorDoug Wiegley <dougw@a10networks.com>
Wed, 18 Jun 2014 20:16:06 +0000 (15:16 -0500)
committerDoug Wiegley <dougw@a10networks.com>
Thu, 26 Jun 2014 06:12:52 +0000 (23:12 -0700)
Implements: bp/lbaas-objmodel-driver-changes
Change-Id: I39dccb93c6f9bd3264afbf0acc55a56fc6fd05a1

etc/neutron.conf
neutron/services/loadbalancer/drivers/abstract_driver.py
neutron/services/loadbalancer/drivers/driver_base.py [new file with mode: 0644]
neutron/services/loadbalancer/drivers/driver_mixins.py [new file with mode: 0644]
neutron/services/loadbalancer/drivers/logging_noop/__init__.py [new file with mode: 0644]
neutron/services/loadbalancer/drivers/logging_noop/driver.py [new file with mode: 0644]
neutron/tests/unit/services/loadbalancer/drivers/logging_noop/__init__.py [new file with mode: 0644]
neutron/tests/unit/services/loadbalancer/drivers/logging_noop/test_logging_noop_driver.py [new file with mode: 0644]

index 31977a8748ede82300c34098a4b59f5d1cd466cf..9bccc86ecbc3d53b54b872c7f073e7c794502c11 100644 (file)
@@ -477,3 +477,5 @@ service_provider=VPN:openswan:neutron.services.vpn.service_drivers.ipsec.IPsecVP
 # service_provider=VPN:cisco:neutron.services.vpn.service_drivers.cisco_ipsec.CiscoCsrIPsecVPNDriver:default
 # Uncomment the line below to use Embrane heleos as Load Balancer service provider.
 # service_provider=LOADBALANCER:Embrane:neutron.services.loadbalancer.drivers.embrane.driver.EmbraneLbaas:default
+# Uncomment the following line to test the LBaaS v2 API _WITHOUT_ a real backend
+# service_provider = LOADBALANCER:LoggingNoop:neutron.services.loadbalancer.drivers.logging_noop.driver.LoggingNoopLoadBalancerDriver:default
index 61123c307e2952c372e34aaf698ad4e7e324caaf..64854fe43459f8d09adde72ff658e4cb0c451a51 100644 (file)
@@ -18,6 +18,14 @@ import abc
 
 import six
 
+#
+# DEPRECATION WARNING.  THIS ABSTRACT DRIVER IS FOR THE LBAAS V1 OBJECT
+# MODEL AND SHOULD NO LONGER BE USED TO CREATE DRIVERS.
+#
+# PLEASE REFER TO driver_base.py and driver_mixins.py for the newest
+# lbaas driver base classes.
+#
+
 
 @six.add_metaclass(abc.ABCMeta)
 class LoadBalancerAbstractDriver(object):
diff --git a/neutron/services/loadbalancer/drivers/driver_base.py b/neutron/services/loadbalancer/drivers/driver_base.py
new file mode 100644 (file)
index 0000000..8ed1f3d
--- /dev/null
@@ -0,0 +1,87 @@
+# Copyright 2014 A10 Networks
+#
+#    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 neutron.db.loadbalancer import loadbalancer_db as lb_db
+from neutron.services.loadbalancer.drivers import driver_mixins
+
+
+class NotImplementedManager(object):
+    """Helper class to make any subclass of LBAbstractDriver explode if it
+    is missing any of the required object managers.
+    """
+
+    def create(self, context, obj):
+        raise NotImplementedError()
+
+    def update(self, context, old_obj, obj):
+        raise NotImplementedError()
+
+    def delete(self, context, obj):
+        raise NotImplementedError()
+
+
+class LoadBalancerBaseDriver(object):
+    """LBaaSv2 object model drivers should subclass LBAbstractDriver, and
+    initialize the following manager classes to create, update, and delete
+    the various load balancer objects.
+    """
+
+    load_balancer = NotImplementedManager()
+    listener = NotImplementedManager()
+    pool = NotImplementedManager()
+    member = NotImplementedManager()
+    health_monitor = NotImplementedManager()
+
+    def __init__(self, plugin):
+        self.plugin = plugin
+
+
+class BaseLoadBalancerManager(driver_mixins.BaseRefreshMixin,
+                              driver_mixins.BaseStatsMixin,
+                              driver_mixins.BaseStatusUpdateMixin,
+                              driver_mixins.BaseManagerMixin):
+
+    def __init__(self, driver):
+        super(BaseLoadBalancerManager, self).__init__(driver)
+        # TODO(dougw), use lb_db.LoadBalancer when v2 lbaas
+        # TODO(dougw), get rid of __init__() in StatusHelperManager, and
+        # the if is not None clauses; after fixing this next line,
+        # it can become a mandatory variable for that subclass.
+        self.model_class = None
+
+
+class BaseListenerManager(driver_mixins.BaseManagerMixin):
+    pass
+
+
+class BasePoolManager(driver_mixins.BaseStatusUpdateMixin,
+                      driver_mixins.BaseManagerMixin):
+
+    def __init__(self, driver):
+        super(BasePoolManager, self).__init__(driver)
+        self.model_class = lb_db.Pool
+
+
+class BaseMemberManager(driver_mixins.BaseStatusUpdateMixin,
+                        driver_mixins.BaseManagerMixin):
+
+    def __init__(self, driver):
+        super(BaseMemberManager, self).__init__(driver)
+        self.model_class = lb_db.Member
+
+
+class BaseHealthMonitorManager(
+                              driver_mixins.BaseHealthMonitorStatusUpdateMixin,
+                              driver_mixins.BaseManagerMixin):
+    pass
diff --git a/neutron/services/loadbalancer/drivers/driver_mixins.py b/neutron/services/loadbalancer/drivers/driver_mixins.py
new file mode 100644 (file)
index 0000000..3afc921
--- /dev/null
@@ -0,0 +1,85 @@
+# Copyright 2014 A10 Networks
+#
+#    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 abc
+import six
+
+from neutron.plugins.common import constants
+
+
+@six.add_metaclass(abc.ABCMeta)
+class BaseManagerMixin(object):
+
+    def __init__(self, driver):
+        self.driver = driver
+
+    @abc.abstractmethod
+    def create(self, context, obj):
+        pass
+
+    @abc.abstractmethod
+    def update(self, context, obj_old, obj):
+        pass
+
+    @abc.abstractmethod
+    def delete(self, context, obj):
+        pass
+
+
+@six.add_metaclass(abc.ABCMeta)
+class BaseRefreshMixin(object):
+
+    @abc.abstractmethod
+    def refresh(self, context, obj):
+        pass
+
+
+@six.add_metaclass(abc.ABCMeta)
+class BaseStatsMixin(object):
+
+    @abc.abstractmethod
+    def stats(self, context, obj):
+        pass
+
+
+class BaseStatusUpdateMixin(object):
+
+    # Status update helpers
+    # Note: You must set self.model_class to an appropriate neutron model
+    # in your base manager class.
+
+    def active(self, context, model_id):
+        if self.model_class is not None:
+            self.driver.plugin.update_status(context, self.model_class,
+                                             model_id, constants.ACTIVE)
+
+    def failed(self, context, model_id):
+        if self.model_class is not None:
+            self.driver.plugin.update_status(context, self.model_class,
+                                             model_id, constants.ERROR)
+
+
+class BaseHealthMonitorStatusUpdateMixin(object):
+
+    def active(self, context, health_monitor_id, pool_id):
+        self.driver.plugin.update_pool_health_monitor(context,
+                                                      health_monitor_id,
+                                                      pool_id,
+                                                      constants.ACTIVE)
+
+    def failed(self, context, health_monitor_id, pool_id):
+        self.driver.plugin.update_pool_health_monitor(context,
+                                                      health_monitor_id,
+                                                      pool_id,
+                                                      constants.ERROR)
diff --git a/neutron/services/loadbalancer/drivers/logging_noop/__init__.py b/neutron/services/loadbalancer/drivers/logging_noop/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/services/loadbalancer/drivers/logging_noop/driver.py b/neutron/services/loadbalancer/drivers/logging_noop/driver.py
new file mode 100644 (file)
index 0000000..e4bd651
--- /dev/null
@@ -0,0 +1,106 @@
+# Copyright 2014, Doug Wiegley (dougwig), A10 Networks
+#
+#    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 neutron.openstack.common import log as logging
+from neutron.services.loadbalancer.drivers import driver_base
+
+LOG = logging.getLogger(__name__)
+
+
+class LoggingNoopLoadBalancerDriver(driver_base.LoadBalancerBaseDriver):
+
+    def __init__(self, plugin):
+        self.plugin = plugin
+
+        # Each of the major LBaaS objects in the neutron database
+        # need a corresponding manager/handler class.
+        #
+        # Put common things that are shared across the entire driver, like
+        # config or a rest client handle, here.
+        #
+        # This function is executed when neutron-server starts.
+
+        self.load_balancer = LoggingNoopLoadBalancerManager(self)
+        self.listener = LoggingNoopListenerManager(self)
+        self.pool = LoggingNoopPoolManager(self)
+        self.member = LoggingNoopMemberManager(self)
+        self.health_monitor = LoggingNoopHealthMonitorManager(self)
+
+
+class LoggingNoopCommonManager(object):
+
+    def create(self, context, obj):
+        LOG.debug("LB %s no-op, create %s", self.__class__.__name__, obj.id)
+        self.active(context, obj.id)
+
+    def update(self, context, old_obj, obj):
+        LOG.debug("LB %s no-op, update %s", self.__class__.__name__, obj.id)
+        self.active(context, obj.id)
+
+    def delete(self, context, obj):
+        LOG.debug("LB %s no-op, delete %s", self.__class__.__name__, obj.id)
+
+
+class LoggingNoopLoadBalancerManager(LoggingNoopCommonManager,
+                                     driver_base.BaseLoadBalancerManager):
+
+    def refresh(self, context, lb_obj, force=False):
+        # This is intended to trigger the backend to check and repair
+        # the state of this load balancer and all of its dependent objects
+        LOG.debug("LB pool refresh %s, force=%s", lb_obj.id, force)
+
+    def stats(self, context, lb_obj):
+        LOG.debug("LB stats %s", lb_obj.id)
+        return {
+            "bytes_in": 0,
+            "bytes_out": 0,
+            "active_connections": 0,
+            "total_connections": 0
+        }
+
+
+class LoggingNoopListenerManager(LoggingNoopCommonManager,
+                                 driver_base.BaseListenerManager):
+
+    def create(self, context, obj):
+        LOG.debug("LB listener no-op, create %s", self.__class__.__name__,
+                  obj.id)
+
+    def update(self, context, old_obj, obj):
+        LOG.debug("LB listener no-op, update %s", self.__class__.__name__,
+                  obj.id)
+
+
+class LoggingNoopPoolManager(LoggingNoopCommonManager,
+                             driver_base.BasePoolManager):
+    pass
+
+
+class LoggingNoopMemberManager(LoggingNoopCommonManager,
+                               driver_base.BaseMemberManager):
+    pass
+
+
+class LoggingNoopHealthMonitorManager(LoggingNoopCommonManager,
+                                      driver_base.BaseHealthMonitorManager):
+
+    def create(self, context, obj):
+        LOG.debug("LB health monitor no-op, create %s",
+                  self.__class__.__name__, obj.id)
+        self.active(context, obj.id, obj.id)
+
+    def update(self, context, old_obj, obj):
+        LOG.debug("LB health monitor no-op, update %s",
+                  self.__class__.__name__, obj.id)
+        self.active(context, obj.id, obj.id)
diff --git a/neutron/tests/unit/services/loadbalancer/drivers/logging_noop/__init__.py b/neutron/tests/unit/services/loadbalancer/drivers/logging_noop/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/tests/unit/services/loadbalancer/drivers/logging_noop/test_logging_noop_driver.py b/neutron/tests/unit/services/loadbalancer/drivers/logging_noop/test_logging_noop_driver.py
new file mode 100644 (file)
index 0000000..be695e8
--- /dev/null
@@ -0,0 +1,148 @@
+# Copyright 2014, Doug Wiegley (dougwig), A10 Networks
+#
+#    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 neutron import context
+from neutron.services.loadbalancer.drivers.logging_noop import driver
+from neutron.tests.unit.db.loadbalancer import test_db_loadbalancer
+
+log_path = 'neutron.services.loadbalancer.drivers.logging_noop.driver.LOG'
+
+
+class FakeModel(object):
+    def __init__(self, id):
+        self.id = id
+
+
+def patch_manager(func):
+    @mock.patch(log_path)
+    def wrapper(*args):
+        log_mock = args[-1]
+        manager_test = args[0]
+        model = args[1]
+        parent = manager_test.parent
+        driver = parent.driver
+        driver.plugin.reset_mock()
+
+        func(*args[:-1])
+
+        s = str(log_mock.mock_calls[0])
+        parent.assertEqual(s[:11], "call.debug(")
+        parent.assertTrue(s.index(model.id) != -1,
+                          msg="Model ID not found in log")
+
+    return wrapper
+
+
+class ManagerTest(object):
+    def __init__(self, parent, manager, model):
+        self.parent = parent
+        self.manager = manager
+
+        self.create(model)
+        self.update(model, model)
+        self.delete(model)
+
+    @patch_manager
+    def create(self, model):
+        self.manager.create(self.parent.context, model)
+
+    @patch_manager
+    def update(self, old_model, model):
+        self.manager.update(self.parent.context, old_model, model)
+
+    @patch_manager
+    def delete(self, model):
+        self.manager.delete(self.parent.context, model)
+
+
+class ManagerTestWithUpdates(ManagerTest):
+    def __init__(self, parent, manager, model):
+        self.parent = parent
+        self.manager = manager
+
+        self.create(model)
+        self.update(model, model)
+        self.delete(model)
+
+    @patch_manager
+    def create(self, model):
+        self.manager.create(self.parent.context, model)
+        if self.manager.model_class is not None:
+            self.parent.assertEqual(
+                             str(self.parent.driver.plugin.mock_calls[0])[:18],
+                             "call.update_status")
+
+    @patch_manager
+    def update(self, old_model, model):
+        self.manager.update(self.parent.context, old_model, model)
+        if self.manager.model_class is not None:
+            self.parent.assertEqual(
+                             str(self.parent.driver.plugin.mock_calls[0])[:18],
+                             "call.update_status")
+
+    @patch_manager
+    def delete(self, model):
+        self.manager.delete(self.parent.context, model)
+
+
+class LoadBalancerManagerTest(ManagerTestWithUpdates):
+    def __init__(self, parent, manager, model):
+        super(LoadBalancerManagerTest, self).__init__(parent, manager, model)
+
+        self.refresh(model)
+        self.stats(model)
+
+    @patch_manager
+    def refresh(self, model):
+        self.manager.refresh(self.parent.context, model)
+
+    @patch_manager
+    def stats(self, model):
+        dummy_stats = {
+            "bytes_in": 0,
+            "bytes_out": 0,
+            "active_connections": 0,
+            "total_connections": 0
+        }
+        h = self.manager.stats(self.parent.context, model)
+        self.parent.assertEqual(h, dummy_stats)
+
+
+class TestLoggingNoopLoadBalancerDriver(
+        test_db_loadbalancer.LoadBalancerPluginDbTestCase):
+
+    def setUp(self):
+        super(TestLoggingNoopLoadBalancerDriver, self).setUp()
+        self.context = context.get_admin_context()
+        self.plugin = mock.Mock()
+        self.driver = driver.LoggingNoopLoadBalancerDriver(self.plugin)
+
+    def test_load_balancer_ops(self):
+        LoadBalancerManagerTest(self, self.driver.load_balancer,
+                                FakeModel("loadbalancer-001"))
+
+    def test_listener_ops(self):
+        ManagerTest(self, self.driver.listener, FakeModel("listener-001"))
+
+    def test_pool_ops(self):
+        ManagerTestWithUpdates(self, self.driver.pool, FakeModel("pool-001"))
+
+    def test_member_ops(self):
+        ManagerTestWithUpdates(self, self.driver.member,
+                               FakeModel("member-001"))
+
+    def test_health_monitor_ops(self):
+        ManagerTest(self, self.driver.health_monitor, FakeModel("hm-001"))