From b533a8ee3fbfb6a7bbe1fc0884dcca95385b99af Mon Sep 17 00:00:00 2001 From: Tong Liu Date: Tue, 30 May 2017 13:57:54 -0700 Subject: [PATCH] NSXv3: Neutron LBaaS nsxv3 support This patch adds neutron lBaaS nsxv3 support. The refactor has been done in neutron-lbaas to share the same driver for nsxv and nsxv3. (I7308035d38f2ab15a85096ec30388ef5c3f56ca3). Also load balancer API wrapper for nsxv3 also has been added to vmware-nsxlib in (I0fc80e20551e0994888d8c222a9a620dcb2f6e32). This patch implements the functionality of the following lbaas resources: - loadbalancer - listener - pool - member - healthmonitor If nsx platform doesn't support LB, we will return a dummy driver class. It will raise NotImplementedError if user tries to use LBaaS driver. Note that layer7 support is not in this patch. It will be supported in another path. Change-Id: I43473f41343e7b7499bf3ebdaf0a51fd2644509a --- vmware_nsx/db/db.py | 109 ++++ .../alembic_migrations/versions/EXPAND_HEAD | 2 +- .../ea7a72ab9643_nsxv3_lbaas_mapping.py | 94 ++++ vmware_nsx/db/nsx_models.py | 60 ++ vmware_nsx/plugins/nsx_v3/plugin.py | 16 + vmware_nsx/services/lbaas/base_mgr.py | 18 + vmware_nsx/services/lbaas/lb_const.py | 20 + vmware_nsx/services/lbaas/nsx_v3/__init__.py | 0 .../lbaas/nsx_v3/healthmonitor_mgr.py | 138 +++++ .../services/lbaas/nsx_v3/lb_driver_v2.py | 64 +++ vmware_nsx/services/lbaas/nsx_v3/lb_utils.py | 107 ++++ .../services/lbaas/nsx_v3/listener_mgr.py | 124 +++++ .../services/lbaas/nsx_v3/loadbalancer_mgr.py | 110 ++++ .../services/lbaas/nsx_v3/member_mgr.py | 222 ++++++++ vmware_nsx/services/lbaas/nsx_v3/pool_mgr.py | 115 ++++ .../tests/unit/services/lbaas/__init__.py | 0 .../unit/services/lbaas/test_nsxv3_driver.py | 519 ++++++++++++++++++ 17 files changed, 1717 insertions(+), 1 deletion(-) create mode 100644 vmware_nsx/db/migration/alembic_migrations/versions/pike/expand/ea7a72ab9643_nsxv3_lbaas_mapping.py create mode 100644 vmware_nsx/services/lbaas/nsx_v3/__init__.py create mode 100644 vmware_nsx/services/lbaas/nsx_v3/healthmonitor_mgr.py create mode 100644 vmware_nsx/services/lbaas/nsx_v3/lb_driver_v2.py create mode 100644 vmware_nsx/services/lbaas/nsx_v3/lb_utils.py create mode 100644 vmware_nsx/services/lbaas/nsx_v3/listener_mgr.py create mode 100644 vmware_nsx/services/lbaas/nsx_v3/loadbalancer_mgr.py create mode 100644 vmware_nsx/services/lbaas/nsx_v3/member_mgr.py create mode 100644 vmware_nsx/services/lbaas/nsx_v3/pool_mgr.py create mode 100644 vmware_nsx/tests/unit/services/lbaas/__init__.py create mode 100644 vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py diff --git a/vmware_nsx/db/db.py b/vmware_nsx/db/db.py index 3f21163ab6..c56d37fbb6 100644 --- a/vmware_nsx/db/db.py +++ b/vmware_nsx/db/db.py @@ -484,3 +484,112 @@ def save_certificate(session, purpose, cert, pk): def delete_certificate(session, purpose): return (session.query(nsx_models.NsxCertificateRepository). filter_by(purpose=purpose).delete()) + + +def add_nsx_lbaas_loadbalancer_binding(session, loadbalancer_id, + lb_service_id, lb_router_id, + vip_address): + with session.begin(subtransactions=True): + binding = nsx_models.NsxLbaasLoadbalancer( + loadbalancer_id=loadbalancer_id, lb_service_id=lb_service_id, + lb_router_id=lb_router_id, vip_address=vip_address) + session.add(binding) + return binding + + +def get_nsx_lbaas_loadbalancer_binding(session, loadbalancer_id): + try: + return session.query( + nsx_models.NsxLbaasLoadbalancer).filter_by( + loadbalancer_id=loadbalancer_id).one() + except exc.NoResultFound: + return + + +def get_nsx_lbaas_loadbalancer_binding_by_service(session, lb_service_id): + return session.query( + nsx_models.NsxLbaasLoadbalancer).filter_by( + lb_service_id=lb_service_id).all() + + +def delete_nsx_lbaas_loadbalancer_binding(session, loadbalancer_id): + return (session.query(nsx_models.NsxLbaasLoadbalancer). + filter_by(loadbalancer_id=loadbalancer_id).delete()) + + +def add_nsx_lbaas_listener_binding(session, loadbalancer_id, listener_id, + app_profile_id, lb_vs_id): + with session.begin(subtransactions=True): + binding = nsx_models.NsxLbaasListener( + loadbalancer_id=loadbalancer_id, listener_id=listener_id, + app_profile_id=app_profile_id, + lb_vs_id=lb_vs_id) + session.add(binding) + return binding + + +def get_nsx_lbaas_listener_binding(session, loadbalancer_id, listener_id): + try: + return session.query( + nsx_models.NsxLbaasListener).filter_by( + loadbalancer_id=loadbalancer_id, + listener_id=listener_id).one() + except exc.NoResultFound: + return + + +def delete_nsx_lbaas_listener_binding(session, loadbalancer_id, listener_id): + return (session.query(nsx_models.NsxLbaasListener). + filter_by(loadbalancer_id=loadbalancer_id, + listener_id=listener_id).delete()) + + +def add_nsx_lbaas_pool_binding(session, loadbalancer_id, pool_id, lb_pool_id, + lb_vs_id): + with session.begin(subtransactions=True): + binding = nsx_models.NsxLbaasPool(loadbalancer_id=loadbalancer_id, + pool_id=pool_id, + lb_pool_id=lb_pool_id, + lb_vs_id=lb_vs_id) + session.add(binding) + return binding + + +def get_nsx_lbaas_pool_binding(session, loadbalancer_id, pool_id): + try: + return session.query(nsx_models.NsxLbaasPool).filter_by( + loadbalancer_id=loadbalancer_id, pool_id=pool_id).one() + except exc.NoResultFound: + return + + +def delete_nsx_lbaas_pool_binding(session, loadbalancer_id, pool_id): + return (session.query(nsx_models.NsxLbaasPool). + filter_by(loadbalancer_id=loadbalancer_id, + pool_id=pool_id).delete()) + + +def add_nsx_lbaas_monitor_binding(session, loadbalancer_id, pool_id, hm_id, + lb_monitor_id, lb_pool_id): + with session.begin(subtransactions=True): + binding = nsx_models.NsxLbaasMonitor( + loadbalancer_id=loadbalancer_id, pool_id=pool_id, hm_id=hm_id, + lb_monitor_id=lb_monitor_id, lb_pool_id=lb_pool_id) + session.add(binding) + return binding + + +def get_nsx_lbaas_monitor_binding(session, loadbalancer_id, pool_id, hm_id): + try: + return session.query(nsx_models.NsxLbaasMonitor).filter_by( + loadbalancer_id=loadbalancer_id, + pool_id=pool_id, hm_id=hm_id).one() + except exc.NoResultFound: + return + + +def delete_nsx_lbaas_monitor_binding(session, loadbalancer_id, pool_id, + hm_id): + return (session.query(nsx_models.NsxLbaasMonitor). + filter_by(loadbalancer_id=loadbalancer_id, + pool_id=pool_id, hm_id=hm_id).delete()) diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD b/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD index 1cb6a415f2..d3fabba9d4 100644 --- a/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -53eb497903a4 +ea7a72ab9643 diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/pike/expand/ea7a72ab9643_nsxv3_lbaas_mapping.py b/vmware_nsx/db/migration/alembic_migrations/versions/pike/expand/ea7a72ab9643_nsxv3_lbaas_mapping.py new file mode 100644 index 0000000000..f198501f84 --- /dev/null +++ b/vmware_nsx/db/migration/alembic_migrations/versions/pike/expand/ea7a72ab9643_nsxv3_lbaas_mapping.py @@ -0,0 +1,94 @@ +# Copyright 2017 VMware, Inc. +# +# 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 alembic import op +import sqlalchemy as sa + +from neutron.db import migration + +"""nsxv3_lbaas_mapping + +Revision ID: ea7a72ab9643 +Revises: 53eb497903a4 +Create Date: 2017-06-12 16:59:48.021909 + +""" + +# revision identifiers, used by Alembic. +revision = 'ea7a72ab9643' +down_revision = '53eb497903a4' + + +def upgrade(): + op.create_table( + 'nsxv3_lbaas_loadbalancers', + sa.Column('loadbalancer_id', sa.String(36), nullable=False), + sa.Column('lb_router_id', sa.String(36), nullable=False), + sa.Column('lb_service_id', sa.String(36), nullable=False), + sa.Column('vip_address', sa.String(36), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('loadbalancer_id')) + op.create_table( + 'nsxv3_lbaas_listeners', + sa.Column('loadbalancer_id', sa.String(36), nullable=False), + sa.Column('listener_id', sa.String(36), nullable=False), + sa.Column('app_profile_id', sa.String(36), nullable=False), + sa.Column('lb_vs_id', sa.String(36), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('loadbalancer_id', 'listener_id')) + + op.create_table( + 'nsxv3_lbaas_pools', + sa.Column('loadbalancer_id', sa.String(36), nullable=False), + sa.Column('pool_id', sa.String(36), nullable=False), + sa.Column('lb_pool_id', sa.String(36), nullable=False), + sa.Column('lb_vs_id', sa.String(36), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('loadbalancer_id', 'pool_id')) + + op.create_table( + 'nsxv3_lbaas_monitors', + sa.Column('loadbalancer_id', sa.String(36), nullable=False), + sa.Column('pool_id', sa.String(36), nullable=False), + sa.Column('hm_id', sa.String(36), nullable=False), + sa.Column('lb_monitor_id', sa.String(36), nullable=False), + sa.Column('lb_pool_id', sa.String(36), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=True), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('loadbalancer_id', 'pool_id', 'hm_id')) + + if migration.schema_has_table('lbaas_loadbalancers'): + op.create_foreign_key( + 'fk_nsxv3_lbaas_loadbalancers_id', 'nsxv3_lbaas_loadbalancers', + 'lbaas_loadbalancers', ['loadbalancer_id'], ['id'], + ondelete='CASCADE') + + if migration.schema_has_table('lbaas_listeners'): + op.create_foreign_key( + 'fk_nsxv3_lbaas_listeners_id', 'nsxv3_lbaas_listeners', + 'lbaas_listeners', ['listener_id'], ['id'], ondelete='CASCADE') + + if migration.schema_has_table('lbaas_pools'): + op.create_foreign_key( + 'fk_nsxv3_lbaas_pools_id', 'nsxv3_lbaas_pools', + 'lbaas_pools', ['pool_id'], ['id'], ondelete='CASCADE') + + if migration.schema_has_table('lbaas_healthmonitors'): + op.create_foreign_key( + 'fk_nsxv3_lbaas_healthmonitors_id', 'nsxv3_lbaas_monitors', + 'lbaas_healthmonitors', ['hm_id'], ['id'], ondelete='CASCADE') diff --git a/vmware_nsx/db/nsx_models.py b/vmware_nsx/db/nsx_models.py index 02003ab021..a75d0b8ac5 100644 --- a/vmware_nsx/db/nsx_models.py +++ b/vmware_nsx/db/nsx_models.py @@ -388,3 +388,63 @@ class NsxCertificateRepository(model_base.BASEV2, models.TimestampMixin): primary_key=True) certificate = sa.Column(sa.String(9216), nullable=False) private_key = sa.Column(sa.String(5120), nullable=False) + + +class NsxLbaasLoadbalancer(model_base.BASEV2, models.TimestampMixin): + """Stores mapping of LBaaS loadbalancer and NSX LB service and router + + Since in NSXv3, multiple loadbalancers may share the same LB service + on NSX backend. And the in turn LB service attaches to a logical router. + This stores the mapping between LBaaS loadbalancer and NSX LB service id + and NSX logical router id. + """ + __tablename__ = 'nsxv3_lbaas_loadbalancers' + fk_name = 'fk_nsxv3_lbaas_loadbalancers_id' + loadbalancer_id = sa.Column(sa.String(36), + sa.ForeignKey('lbaas_loadbalancers.id', + name=fk_name, + ondelete="CASCADE"), + primary_key=True) + lb_router_id = sa.Column(sa.String(36), nullable=False) + lb_service_id = sa.Column(sa.String(36), nullable=False) + vip_address = sa.Column(sa.String(36), nullable=False) + + +class NsxLbaasListener(model_base.BASEV2, models.TimestampMixin): + """Stores the mapping between LBaaS listener and NSX LB virtual server""" + __tablename__ = 'nsxv3_lbaas_listeners' + loadbalancer_id = sa.Column(sa.String(36), primary_key=True) + listener_id = sa.Column(sa.String(36), + sa.ForeignKey('lbaas_listeners.id', + name='fk_nsxv3_lbaas_listeners_id', + ondelete="CASCADE"), + primary_key=True) + app_profile_id = sa.Column(sa.String(36), nullable=False) + lb_vs_id = sa.Column(sa.String(36), nullable=False) + + +class NsxLbaasPool(model_base.BASEV2, models.TimestampMixin): + """Stores the mapping between LBaaS pool and NSX LB Pool""" + __tablename__ = 'nsxv3_lbaas_pools' + loadbalancer_id = sa.Column(sa.String(36), primary_key=True) + pool_id = sa.Column(sa.String(36), + sa.ForeignKey('lbaas_pools.id', + name='fk_nsxv3_lbaas_pools_id', + ondelete="CASCADE"), + primary_key=True) + lb_pool_id = sa.Column(sa.String(36), nullable=False) + lb_vs_id = sa.Column(sa.String(36), nullable=False) + + +class NsxLbaasMonitor(model_base.BASEV2, models.TimestampMixin): + """Stores the mapping between LBaaS monitor and NSX LB monitor""" + __tablename__ = 'nsxv3_lbaas_monitors' + loadbalancer_id = sa.Column(sa.String(36), primary_key=True) + pool_id = sa.Column(sa.String(36), primary_key=True) + hm_id = sa.Column(sa.String(36), + sa.ForeignKey('lbaas_healthmonitors.id', + name='fk_nsxv3_lbaas_healthmonitors_id', + ondelete="CASCADE"), + primary_key=True) + lb_monitor_id = sa.Column(sa.String(36), nullable=False) + lb_pool_id = sa.Column(sa.String(36), nullable=False) diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index 260ba698c7..da0c6efd8a 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -94,6 +94,7 @@ from vmware_nsx.plugins.common import plugin as nsx_plugin_common from vmware_nsx.plugins.nsx_v3 import availability_zones as nsx_az from vmware_nsx.plugins.nsx_v3 import utils as v3_utils from vmware_nsx.services.fwaas.nsx_v3 import fwaas_callbacks +from vmware_nsx.services.lbaas.nsx_v3 import lb_driver_v2 from vmware_nsx.services.qos.common import utils as qos_com_utils from vmware_nsx.services.qos.nsx_v3 import driver as qos_driver from vmware_nsx.services.trunk.nsx_v3 import driver as trunk_driver @@ -184,6 +185,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, self._extension_manager.extension_aliases()) self.nsxlib = v3_utils.get_nsxlib_wrapper() + self.lbv2_driver = self._init_lbv2_driver() # reinitialize the cluster upon fork for api workers to ensure each # process has its own keepalive loops + state registry.subscribe( @@ -250,6 +252,18 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, # Bind FWaaS callbacks to the driver self.fwaas_callbacks = fwaas_callbacks.Nsxv3FwaasCallbacks(self.nsxlib) + def _init_lbv2_driver(self): + # Get LBaaSv2 driver during plugin initialization. If the platform + # has a version that doesn't support native loadbalancing, the driver + # will return a NotImplementedManager class. + LOG.debug("Initializing LBaaSv2.0 nsxv3 driver") + if self.nsxlib.feature_supported(nsxlib_consts.FEATURE_LOAD_BALANCER): + return lb_driver_v2.EdgeLoadbalancerDriverV2() + else: + LOG.warning("Current NSX version %(ver)s doesn't support LBaaS", + {'ver': self.nsxlib.get_version()}) + return lb_driver_v2.DummyLoadbalancerDriverV2() + def init_availability_zones(self): # availability zones are supported only with native dhcp # if not - the default az will be loaded and used internally only @@ -2815,6 +2829,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return self.get_router(context, router['id']) def delete_router(self, context, router_id): + # TODO(tongl) Prevent router deletion if router still has load + # balancer attachment. Raise exception if router has lb attachment. if not cfg.CONF.nsx_v3.native_dhcp_metadata: nsx_rpc.handle_router_metadata_access(self, context, router_id, interface=None) diff --git a/vmware_nsx/services/lbaas/base_mgr.py b/vmware_nsx/services/lbaas/base_mgr.py index ae890d9217..c9f719f258 100644 --- a/vmware_nsx/services/lbaas/base_mgr.py +++ b/vmware_nsx/services/lbaas/base_mgr.py @@ -15,11 +15,15 @@ from neutron_lib.plugins import constants as plugin_const from neutron_lib.plugins import directory +from oslo_log import log as logging + +LOG = logging.getLogger(__name__) class LoadbalancerBaseManager(object): _lbv2_driver = None _core_plugin = None + _flavor_plugin = None def __init__(self): super(LoadbalancerBaseManager, self).__init__() @@ -45,6 +49,14 @@ class LoadbalancerBaseManager(object): return LoadbalancerBaseManager._core_plugin + @property + def flavor_plugin(self): + if not LoadbalancerBaseManager._flavor_plugin: + LoadbalancerBaseManager._flavor_plugin = ( + self._get_plugin(plugin_const.FLAVORS)) + + return LoadbalancerBaseManager._flavor_plugin + class EdgeLoadbalancerBaseManager(LoadbalancerBaseManager): @@ -55,3 +67,9 @@ class EdgeLoadbalancerBaseManager(LoadbalancerBaseManager): @property def vcns(self): return self.vcns_driver.vcns + + +class Nsxv3LoadbalancerBaseManager(LoadbalancerBaseManager): + + def __init__(self): + super(Nsxv3LoadbalancerBaseManager, self).__init__() diff --git a/vmware_nsx/services/lbaas/lb_const.py b/vmware_nsx/services/lbaas/lb_const.py index 4835e3f88c..80899f5b27 100644 --- a/vmware_nsx/services/lbaas/lb_const.py +++ b/vmware_nsx/services/lbaas/lb_const.py @@ -72,3 +72,23 @@ L7_RULE_COMPARE_TYPE_STARTS_WITH = 'STARTS_WITH' L7_RULE_COMPARE_TYPE_ENDS_WITH = 'ENDS_WITH' L7_RULE_COMPARE_TYPE_CONTAINS = 'CONTAINS' L7_RULE_COMPARE_TYPE_EQUAL_TO = 'EQUAL_TO' + +# Resource type for resources created on NSX backend +LB_LB_TYPE = 'os-lbaas-lb-id' +LB_LISTENER_TYPE = 'os-lbaas-listener-id' +LB_HM_TYPE = 'os-lbaas-hm-id' +LB_POOL_TYPE = 'os-lbaas-pool-type' +LB_HTTP_PROFILE = 'LbHttpProfile' +LB_TCP_PROFILE = 'LbFastTcpProfile' +NSXV3_MONITOR_MAP = {LB_HEALTH_MONITOR_PING: 'LbIcmpMonitor', + LB_HEALTH_MONITOR_TCP: 'LbTcpMonitor', + LB_HEALTH_MONITOR_HTTP: 'LbHttpMonitor', + LB_HEALTH_MONITOR_HTTPS: 'LbHttpsMonitor'} +LB_STATS_MAP = {'active_connections': 'current_sessions', + 'bytes_in': 'bytes_in', + 'bytes_out': 'bytes_out', + 'total_connections': 'total_sessions'} +LR_ROUTER_TYPE = 'os-neutron-router-id' +LR_PORT_TYPE = 'os-neutron-rport-id' +DEFAULT_LB_SIZE = 'SMALL' +LB_FLAVOR_SIZES = ['SMALL', 'MEDIUM', 'LARGE', 'small', 'medium', 'large'] diff --git a/vmware_nsx/services/lbaas/nsx_v3/__init__.py b/vmware_nsx/services/lbaas/nsx_v3/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vmware_nsx/services/lbaas/nsx_v3/healthmonitor_mgr.py b/vmware_nsx/services/lbaas/nsx_v3/healthmonitor_mgr.py new file mode 100644 index 0000000000..dae0df39c6 --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v3/healthmonitor_mgr.py @@ -0,0 +1,138 @@ +# Copyright 2017 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib import exceptions as n_exc +from oslo_log import helpers as log_helpers +from oslo_log import log as logging +from oslo_utils import excutils + +from vmware_nsx._i18n import _ +from vmware_nsx.db import db as nsx_db +from vmware_nsx.services.lbaas import base_mgr +from vmware_nsx.services.lbaas import lb_const +from vmware_nsx.services.lbaas.nsx_v3 import lb_utils +from vmware_nsxlib.v3 import exceptions as nsxlib_exc +from vmware_nsxlib.v3 import utils + +LOG = logging.getLogger(__name__) + + +class EdgeHealthMonitorManager(base_mgr.Nsxv3LoadbalancerBaseManager): + + @log_helpers.log_method_call + def __init__(self): + super(EdgeHealthMonitorManager, self).__init__() + + @log_helpers.log_method_call + def _build_monitor_args(self, hm): + if hm.type in lb_const.NSXV3_MONITOR_MAP: + monitor_type = lb_const.NSXV3_MONITOR_MAP.get(hm.type) + else: + msg = (_('Cannot create health monitor %(monitor)s with ' + 'type %(type)s') % {'monitor': hm.id, 'type': hm.type}) + raise n_exc.InvalidInput(error_message=msg) + body = {'resource_type': monitor_type, + 'interval': hm.delay, + 'fall_count': hm.max_retries, + 'timeout': hm.timeout} + if monitor_type in [lb_const.LB_HEALTH_MONITOR_HTTP, + lb_const.LB_HEALTH_MONITOR_HTTPS]: + if hm.http_method: + body['request_method'] = hm.http_method + if hm.url_path: + body['request_url'] = hm.url_path + # TODO(tongl): nsxv3 backend doesn't support granular control + # of expected_codes. So we ignore it and use default for now. + # Once backend supports it, we can add it back. + # if hm.expected_codes: + # body['response_status'] = hm.expected_codes + return body + + @log_helpers.log_method_call + def create(self, context, hm): + lb_id = hm.pool.loadbalancer_id + pool_id = hm.pool.id + pool_client = self.core_plugin.nsxlib.load_balancer.pool + monitor_client = self.core_plugin.nsxlib.load_balancer.monitor + monitor_name = utils.get_name_and_uuid(hm.name, hm.id) + tags = lb_utils.get_tags(self.core_plugin, hm.id, lb_const.LB_HM_TYPE, + hm.tenant_id, context.project_name) + monitor_body = self._build_monitor_args(hm) + + try: + lb_monitor = monitor_client.create( + display_name=monitor_name, tags=tags, **monitor_body) + except nsxlib_exc.ManagerError: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.health_monitor.failed_completion(context, hm) + + binding = nsx_db.get_nsx_lbaas_pool_binding( + context.session, lb_id, pool_id) + if binding: + lb_pool_id = binding['lb_pool_id'] + try: + pool_client.add_monitor_to_pool(lb_pool_id, + lb_monitor['id']) + except nsxlib_exc.ManagerError: + self.lbv2_driver.health_monitor.failed_completion( + context, hm) + msg = _('Failed to attach monitor %(monitor)s to pool ' + '%(pool)s') % {'monitor': lb_monitor['id'], + 'pool': lb_pool_id} + raise n_exc.BadRequest(resource='lbaas-hm', msg=msg) + nsx_db.add_nsx_lbaas_monitor_binding( + context.session, lb_id, pool_id, hm.id, lb_monitor['id'], + lb_pool_id) + + self.lbv2_driver.health_monitor.successful_completion(context, hm) + + @log_helpers.log_method_call + def update(self, context, old_hm, new_hm): + self.lbv2_driver.health_monitor.successful_completion(context, new_hm) + + @log_helpers.log_method_call + def delete(self, context, hm): + lb_id = hm.pool.loadbalancer_id + pool_id = hm.pool.id + pool_client = self.core_plugin.nsxlib.load_balancer.pool + monitor_client = self.core_plugin.nsxlib.load_balancer.monitor + + binding = nsx_db.get_nsx_lbaas_monitor_binding( + context.session, lb_id, pool_id, hm.id) + if binding: + lb_monitor_id = binding['lb_monitor_id'] + lb_pool_id = binding['lb_pool_id'] + try: + pool_client.remove_monitor_from_pool(lb_pool_id, + lb_monitor_id) + except nsxlib_exc.ManagerError: + self.lbv2_driver.health_monitor.failed_completion( + context, hm) + msg = _('Failed to remove monitor %(monitor)s from pool ' + '%(pool)s') % {'monitor': lb_monitor_id, + 'pool': lb_pool_id} + raise n_exc.BadRequest(resource='lbaas-hm', msg=msg) + try: + monitor_client.delete(lb_monitor_id) + except nsxlib_exc.ManagerError: + self.lbv2_driver.health_monitor.failed_completion( + context, hm) + msg = _('Failed to delete monitor %(monitor)s from ' + 'backend') % {'monitor': lb_monitor_id} + raise n_exc.BadRequest(resource='lbaas-hm', msg=msg) + nsx_db.delete_nsx_lbaas_monitor_binding(context.session, lb_id, + pool_id, hm.id) + self.lbv2_driver.health_monitor.successful_completion( + context, hm, delete=True) diff --git a/vmware_nsx/services/lbaas/nsx_v3/lb_driver_v2.py b/vmware_nsx/services/lbaas/nsx_v3/lb_driver_v2.py new file mode 100644 index 0000000000..223e509b2e --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v3/lb_driver_v2.py @@ -0,0 +1,64 @@ +# Copyright 2017 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_log import helpers as log_helpers +from oslo_log import log as logging + +from vmware_nsx.services.lbaas.nsx_v3 import healthmonitor_mgr as hm_mgr +from vmware_nsx.services.lbaas.nsx_v3 import listener_mgr +from vmware_nsx.services.lbaas.nsx_v3 import loadbalancer_mgr as lb_mgr +from vmware_nsx.services.lbaas.nsx_v3 import member_mgr +from vmware_nsx.services.lbaas.nsx_v3 import pool_mgr + +LOG = logging.getLogger(__name__) + + +class NotImplementedManager(object): + """Helper class to make any subclass of LoadBalancerBaseDriver 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 EdgeLoadbalancerDriverV2(object): + @log_helpers.log_method_call + def __init__(self): + super(EdgeLoadbalancerDriverV2, self).__init__() + + self.loadbalancer = lb_mgr.EdgeLoadBalancerManager() + self.listener = listener_mgr.EdgeListenerManager() + self.pool = pool_mgr.EdgePoolManager() + self.member = member_mgr.EdgeMemberManager() + self.healthmonitor = hm_mgr.EdgeHealthMonitorManager() + + +class DummyLoadbalancerDriverV2(object): + @log_helpers.log_method_call + def __init__(self): + self.load_balancer = NotImplementedManager() + self.listener = NotImplementedManager() + self.pool = NotImplementedManager() + self.member = NotImplementedManager() + self.health_monitor = NotImplementedManager() + self.l7policy = NotImplementedManager() + self.l7rule = NotImplementedManager() diff --git a/vmware_nsx/services/lbaas/nsx_v3/lb_utils.py b/vmware_nsx/services/lbaas/nsx_v3/lb_utils.py new file mode 100644 index 0000000000..7f6959d36f --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v3/lb_utils.py @@ -0,0 +1,107 @@ +# Copyright 2017 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron.db import l3_db +from neutron.services.flavors import flavors_plugin +from neutron_lib import exceptions as n_exc + +from vmware_nsx._i18n import _ +from vmware_nsx.services.lbaas import lb_const +from vmware_nsxlib.v3 import utils + + +def get_tags(plugin, resource_id, resource_type, project_id, project_name): + resource = {'project_id': project_id, + 'id': resource_id} + tags = plugin.nsxlib.build_v3_tags_payload( + resource, resource_type=resource_type, + project_name=project_name) + return tags + + +def get_nsx_resource_binding(client, name, id): + """ + :param client: nsx resource client + :param name: name of neutron object + :param id: id of neutron object + :return: return the nsx resource id + """ + nsx_name = utils.get_name_and_uuid(name, id) + nsx_resource = client.find_by_display_name(nsx_name) + if nsx_resource: + return nsx_resource[0]['id'] + + +def get_network_from_subnet(context, plugin, subnet_id): + subnet = plugin.get_subnet(context, subnet_id) + if subnet: + return plugin.get_network(context, subnet['network_id']) + + +def get_router_from_network(context, plugin, subnet_id): + subnet = plugin.get_subnet(context, subnet_id) + network_id = subnet['network_id'] + port_filters = {'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF], + 'network_id': [network_id]} + ports = plugin.get_ports(context, filters=port_filters) + if ports: + return ports[0]['device_id'] + + +def get_lb_router_id(context, plugin, lb): + router_client = plugin.nsxlib.logical_router + name = utils.get_name_and_uuid(lb.name, lb.id) + tags = get_tags(plugin, lb.id, lb_const.LB_LB_TYPE, lb.tenant_id, + context.project_name) + edge_cluster_uuid = plugin._get_edge_cluster(plugin._default_tier0_router) + lb_router = router_client.create(name, tags, edge_cluster_uuid) + return lb_router + + +def get_lb_flavor_size(flavor_plugin, context, flavor_id): + if not flavor_id: + return lb_const.DEFAULT_LB_SIZE + else: + flavor = flavors_plugin.FlavorsPlugin.get_flavor( + flavor_plugin, context, flavor_id) + flavor_size = flavor['name'] + if flavor_size in lb_const.LB_FLAVOR_SIZES: + return flavor_size.upper() + else: + err_msg = (_("Invalid flavor size %(flavor)s, only 'small', " + "'medium', or 'large' are supported") % + {'flavor': flavor_size}) + raise n_exc.InvalidInput(error_message=err_msg) + + +def validate_lb_subnet(context, plugin, subnet_id): + '''Validate LB subnet before creating loadbalancer on it. + + To create a loadbalancer, the network has to be either an external + network or private network that connects to a tenant router. It will + throw exception if the network doesn't meet this requirement. + + :param context: context + :param plugin: core plugin + :param subnet_id: loadbalancer's subnet id + :return: True if subnet meet requirement, otherwise return False + ''' + network = get_network_from_subnet(context, plugin, subnet_id) + router_id = get_router_from_network( + context, plugin, subnet_id) + if network.get('router:external') or router_id: + return True + else: + return False diff --git a/vmware_nsx/services/lbaas/nsx_v3/listener_mgr.py b/vmware_nsx/services/lbaas/nsx_v3/listener_mgr.py new file mode 100644 index 0000000000..6ae881b217 --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v3/listener_mgr.py @@ -0,0 +1,124 @@ +# Copyright 2017 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib import exceptions as n_exc +from oslo_log import helpers as log_helpers +from oslo_log import log as logging + +from vmware_nsx._i18n import _ +from vmware_nsx.common import exceptions as nsx_exc +from vmware_nsx.db import db as nsx_db +from vmware_nsx.services.lbaas import base_mgr +from vmware_nsx.services.lbaas import lb_const +from vmware_nsx.services.lbaas.nsx_v3 import lb_utils +from vmware_nsxlib.v3 import exceptions as nsxlib_exc +from vmware_nsxlib.v3 import utils + +LOG = logging.getLogger(__name__) + + +class EdgeListenerManager(base_mgr.Nsxv3LoadbalancerBaseManager): + @log_helpers.log_method_call + def __init__(self): + super(EdgeListenerManager, self).__init__() + + @log_helpers.log_method_call + def create(self, context, listener, certificate=None): + lb_id = listener.loadbalancer_id + vip_address = listener.loadbalancer.vip_address + load_balancer = self.core_plugin.nsxlib.load_balancer + app_client = load_balancer.application_profile + vs_client = load_balancer.virtual_server + vs_name = utils.get_name_and_uuid(listener.name, listener.id) + tags = lb_utils.get_tags(self.core_plugin, listener.id, + lb_const.LB_LISTENER_TYPE, + listener.tenant_id, + context.project_name) + if listener.protocol == 'HTTP' or listener.protocol == 'HTTPS': + profile_type = lb_const.LB_HTTP_PROFILE + elif listener.protocol == 'TCP': + profile_type = lb_const.LB_TCP_PROFILE + else: + msg = (_('Cannot create listener %(listener)s with ' + 'protocol %(protocol)s') % + {'listener': listener.id, + 'protocol': listener.protocol}) + raise n_exc.BadRequest(resource='lbaas-listener', msg=msg) + try: + app_profile = app_client.create( + display_name=vs_name, resource_type=profile_type, tags=tags) + app_profile_id = app_profile['id'] + virtual_server = vs_client.create( + display_name=vs_name, + tags=tags, + enabled=listener.admin_state_up, + ip_address=vip_address, + port=listener.protocol_port, + application_profile_id=app_profile_id) + except nsxlib_exc.ManagerError: + self.lbv2_driver.listener.failed_completion(context, listener) + msg = _('Failed to create virtual server at NSX backend') + raise n_exc.BadRequest(resource='lbaas-listener', msg=msg) + + nsx_db.add_nsx_lbaas_listener_binding( + context.session, lb_id, listener.id, app_profile_id, + virtual_server['id']) + self.lbv2_driver.listener.successful_completion( + context, listener) + + @log_helpers.log_method_call + def update(self, context, old_listener, new_listener, certificate=None): + self.lbv2_driver.listener.successful_completion(context, new_listener) + + @log_helpers.log_method_call + def delete(self, context, listener): + lb_id = listener.loadbalancer_id + load_balancer = self.core_plugin.nsxlib.load_balancer + vs_client = load_balancer.virtual_server + app_client = load_balancer.application_profile + + binding = nsx_db.get_nsx_lbaas_listener_binding( + context.session, lb_id, listener.id) + if binding: + vs_id = binding['lb_vs_id'] + app_profile_id = binding['app_profile_id'] + try: + vs_client.delete(vs_id) + except nsx_exc.NsxResourceNotFound: + msg = (_("virtual server not found on nsx: %(vs)s") % + {'vs': vs_id}) + raise n_exc.BadRequest(resource='lbaas-listener', msg=msg) + except nsxlib_exc.ManagerError: + self.lbv2_driver.listener.failed_completion(context, + listener) + msg = (_('Failed to delete virtual server: %(listener)s') % + {'listener': listener.id}) + raise n_exc.BadRequest(resource='lbaas-listener', msg=msg) + try: + app_client.delete(app_profile_id) + except nsx_exc.NsxResourceNotFound: + msg = (_("application profile not found on nsx: %s") % + app_profile_id) + raise n_exc.BadRequest(resource='lbaas-listener', msg=msg) + except nsxlib_exc.ManagerError: + self.lbv2_driver.listener.failed_completion(context, + listener) + msg = (_('Failed to delete application profile: %(app)s') % + {'app': app_profile_id}) + raise n_exc.BadRequest(resource='lbaas-listener', msg=msg) + nsx_db.delete_nsx_lbaas_listener_binding( + context.session, lb_id, listener.id) + self.lbv2_driver.listener.successful_completion( + context, listener, delete=True) diff --git a/vmware_nsx/services/lbaas/nsx_v3/loadbalancer_mgr.py b/vmware_nsx/services/lbaas/nsx_v3/loadbalancer_mgr.py new file mode 100644 index 0000000000..b176e62f45 --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v3/loadbalancer_mgr.py @@ -0,0 +1,110 @@ +# Copyright 2017 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from neutron_lib.callbacks import events +from neutron_lib.callbacks import registry +from neutron_lib.callbacks import resources +from neutron_lib import exceptions as n_exc +from oslo_log import helpers as log_helpers +from oslo_log import log as logging + +from vmware_nsx._i18n import _ +from vmware_nsx.db import db as nsx_db +from vmware_nsx.services.lbaas import base_mgr +from vmware_nsx.services.lbaas import lb_const +from vmware_nsx.services.lbaas.nsx_v3 import lb_utils +from vmware_nsxlib.v3 import exceptions as nsxlib_exc + +LOG = logging.getLogger(__name__) + + +class EdgeLoadBalancerManager(base_mgr.Nsxv3LoadbalancerBaseManager): + @log_helpers.log_method_call + def __init__(self): + super(EdgeLoadBalancerManager, self).__init__() + registry.subscribe( + self._handle_subnet_gw_change, + resources.SUBNET, events.AFTER_UPDATE) + + @log_helpers.log_method_call + def create(self, context, lb): + if lb_utils.validate_lb_subnet(context, self.core_plugin, + lb.vip_subnet_id): + self.lbv2_driver.load_balancer.successful_completion(context, lb) + else: + msg = _('Cannot create lb on subnet %(sub)s for ' + 'loadbalancer %(lb)s as it does not connect ' + 'to router') % {'sub': lb.vip_subnet_id, + 'lb': lb.id} + raise n_exc.BadRequest(resource='lbaas-subnet', msg=msg) + + @log_helpers.log_method_call + def update(self, context, old_lb, new_lb): + self.lbv2_driver.load_balancer.successful_completion(context, new_lb) + + @log_helpers.log_method_call + def delete(self, context, lb): + # Discard any ports which are associated with LB + self.lbv2_driver.load_balancer.successful_completion( + context, lb, delete=True) + + @log_helpers.log_method_call + def refresh(self, context, lb): + # TODO(tongl): implememnt + pass + + @log_helpers.log_method_call + def stats(self, context, lb): + # Since multiple LBaaS loadbalancer can share the same LB service, + # get the corresponding virtual servers' stats instead of LB service. + stats = {'active_connections': 0, + 'bytes_in': 0, + 'bytes_out': 0, + 'total_connections': 0} + + service_client = self.core_plugin.nsxlib.load_balancer.service + for listener in lb.listeners: + lb_binding = nsx_db.get_nsx_lbaas_loadbalancer_binding( + context.session, lb.id) + vs_binding = nsx_db.get_nsx_lbaas_listener_binding( + context.session, lb.id, listener.id) + if lb_binding and vs_binding: + lb_service_id = lb_binding.get('lb_service_id') + vs_id = vs_binding.get('lb_vs_id') + try: + rsp = service_client.get_stats(lb_service_id) + if rsp: + + vs_stats = rsp['virtual_servers'][vs_id]['statistics'] + for stat in lb_const.LB_STATS_MAP: + lb_stat = lb_const.LB_STATS_MAP[stat] + if lb_stat in vs_stats: + stats[stat] += stats[stat] + vs_stats[lb_stat] + + except nsxlib_exc.ManagerError: + msg = _('Failed to retrieve stats from LB service ' + 'for loadbalancer %(lb)s') % {'lb': lb.id} + raise n_exc.BadRequest(resource='lbaas-lb', msg=msg) + return stats + + @log_helpers.log_method_call + def _handle_subnet_gw_change(self, *args, **kwargs): + # As the Edge appliance doesn't use DHCP, we should change the + # default gateway here when the subnet GW changes. + orig = kwargs['original_subnet'] + updated = kwargs['subnet'] + if orig['gateway_ip'] == updated['gateway_ip']: + return diff --git a/vmware_nsx/services/lbaas/nsx_v3/member_mgr.py b/vmware_nsx/services/lbaas/nsx_v3/member_mgr.py new file mode 100644 index 0000000000..af8605f767 --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v3/member_mgr.py @@ -0,0 +1,222 @@ +# Copyright 2017 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib import exceptions as n_exc +from oslo_log import helpers as log_helpers +from oslo_log import log as logging +from oslo_utils import excutils + +from vmware_nsx._i18n import _ +from vmware_nsx.common import exceptions as nsx_exc +from vmware_nsx.db import db as nsx_db +from vmware_nsx.services.lbaas import base_mgr +from vmware_nsx.services.lbaas import lb_const +from vmware_nsx.services.lbaas.nsx_v3 import lb_utils +from vmware_nsxlib.v3 import exceptions as nsxlib_exc +from vmware_nsxlib.v3 import utils + +LOG = logging.getLogger(__name__) + + +class EdgeMemberManager(base_mgr.Nsxv3LoadbalancerBaseManager): + @log_helpers.log_method_call + def __init__(self): + super(EdgeMemberManager, self).__init__() + + @log_helpers.log_method_call + def _get_info_from_fip(self, context, fip): + filters = {'floating_ip_address': [fip]} + floating_ips = self.core_plugin.get_floatingips(context, + filters=filters) + if floating_ips: + return (floating_ips[0]['fixed_ip_address'], + floating_ips[0]['router_id']) + else: + msg = (_('Cannot get floating ip %(fip)s provided from ' + 'neutron db') % {'fip': fip}) + raise nsx_exc.BadRequest(resource='lbaas-vip', msg=msg) + + @log_helpers.log_method_call + def _create_lb_service(self, context, service_client, tenant_id, + router_id, nsx_router_id, lb_id, lb_size): + router = self.core_plugin.get_router(context, router_id) + lb_name = utils.get_name_and_uuid(router['name'], + router_id) + tags = lb_utils.get_tags(self.core_plugin, router_id, + lb_const.LR_ROUTER_TYPE, + tenant_id, context.project_name) + tags.append({'scope': 'os-lbaas-lb-id', 'tag': lb_id}) + attachment = {'target_id': nsx_router_id, + 'target_type': 'LogicalRouter'} + lb_service = service_client.create(display_name=lb_name, + tags=tags, + attachment=attachment, + size=lb_size) + return lb_service + + @log_helpers.log_method_call + def create(self, context, member): + pool_id = member.pool.id + loadbalancer = member.pool.loadbalancer + if not lb_utils.validate_lb_subnet(context, self.core_plugin, + member.subnet_id): + msg = (_('Cannot add member %(member)s to pool as member subnet ' + '%(subnet)s is neither public nor connected to router') % + {'member': member.id, 'subnet': member.subnet_id}) + raise n_exc.BadRequest(resource='lbaas-subnet', msg=msg) + + pool_client = self.core_plugin.nsxlib.load_balancer.pool + service_client = self.core_plugin.nsxlib.load_balancer.service + pool_members = self.lbv2_driver.plugin.get_pool_members( + context, pool_id) + + network = lb_utils.get_network_from_subnet( + context, self.core_plugin, member.subnet_id) + if network.get('router:external'): + router_id, fixed_ip = self._get_info_from_fip( + context, member.address) + else: + router_id = lb_utils.get_router_from_network( + context, self.core_plugin, member.subnet_id) + fixed_ip = member.address + + binding = nsx_db.get_nsx_lbaas_pool_binding(context.session, + loadbalancer.id, pool_id) + if binding: + vs_id = binding['lb_vs_id'] + lb_pool_id = binding['lb_pool_id'] + + if len(pool_members) == 1: + nsx_router_id = nsx_db.get_nsx_router_id(context.session, + router_id) + lb_service = service_client.get_router_lb_service( + nsx_router_id) + if not lb_service: + lb_size = lb_utils.get_lb_flavor_size( + self.flavor_plugin, context, loadbalancer.flavor_id) + lb_service = self._create_lb_service( + context, service_client, member.tenant_id, + router_id, nsx_router_id, loadbalancer.id, lb_size) + if lb_service: + lb_service_id = lb_service['id'] + nsx_db.add_nsx_lbaas_loadbalancer_binding( + context.session, loadbalancer.id, lb_service_id, + nsx_router_id, loadbalancer.vip_address) + try: + service_client.add_virtual_server_to_service( + lb_service_id, vs_id) + except nsxlib_exc.ManagerError: + self.lbv2_driver.member.failed_completion(context, + member) + msg = (_('Failed to attach virtual server %(vs)s to ' + 'lb service %(service)s') % + {'vs': vs_id, 'service': lb_service_id}) + raise n_exc.BadRequest(resource='lbaas-member', + msg=msg) + else: + msg = (_('Failed to get lb service to attach virtual ' + 'server %(vs)s for member %(member)s') % + {'vs': vs_id, 'member': member['id']}) + raise nsx_exc.NsxPluginException(err_msg=msg) + + lb_pool = pool_client.get(lb_pool_id) + old_m = lb_pool.get('members', None) + new_m = [{'display_name': member.name, + 'ip_address': fixed_ip, + 'port': member.protocol_port, + 'weight': member.weight}] + members = (old_m + new_m) if old_m else new_m + pool_client.update_pool_with_members(lb_pool_id, members) + else: + msg = (_('Failed to get pool binding to add member %s') % + member['id']) + raise nsx_exc.NsxPluginException(err_msg=msg) + + self.lbv2_driver.member.successful_completion(context, member) + + @log_helpers.log_method_call + def update(self, context, old_member, new_member): + try: + self.lbv2_driver.member.successful_completion( + context, new_member) + + except nsx_exc.NsxPluginException: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.member.failed_completion( + context, new_member) + + @log_helpers.log_method_call + def delete(self, context, member): + lb_id = member.pool.loadbalancer_id + pool_id = member.pool.id + service_client = self.core_plugin.nsxlib.load_balancer.service + pool_client = self.core_plugin.nsxlib.load_balancer.pool + pool_members = self.lbv2_driver.plugin.get_pool_members( + context, pool_id) + pool_binding = nsx_db.get_nsx_lbaas_pool_binding( + context.session, lb_id, pool_id) + if pool_binding: + lb_pool_id = pool_binding['lb_pool_id'] + lb_vs_id = pool_binding['lb_vs_id'] + # If this is the last member of pool, detach virtual server from + # the lb service. If this is the last load balancer for this lb + # service, delete the lb service as well. + if len(pool_members) == 1: + lb_binding = nsx_db.get_nsx_lbaas_loadbalancer_binding( + context.session, lb_id) + if lb_binding: + lb_service_id = lb_binding['lb_service_id'] + try: + lb_service = service_client.get(lb_service_id) + vs_list = lb_service.get('virtual_server_ids') + if vs_list and lb_vs_id in vs_list: + vs_list.remove(lb_vs_id) + else: + LOG.error('virtual server id %s is not in the lb ' + 'service virtual server list %s', + lb_vs_id, vs_list) + service_client.update(lb_service_id, + virtual_server_ids=vs_list) + if not vs_list: + service_client.delete(lb_service_id) + nsx_db.delete_nsx_lbaas_loadbalancer_binding( + context.session, lb_id) + except nsxlib_exc.ManagerError: + self.lbv2_driver.member.failed_completion(context, + member) + msg = _('Failed to remove virtual server from lb ' + 'service on NSX backend') + raise n_exc.BadRequest(resource='lbaas-member', + msg=msg) + try: + lb_pool = pool_client.get(lb_pool_id) + network = lb_utils.get_network_from_subnet( + context, self.core_plugin, member.subnet_id) + if network.get('router:external'): + fixed_ip, router_id = self._get_info_from_fip( + context, member.address) + else: + fixed_ip = member.address + if 'members' in lb_pool: + m_list = lb_pool['members'] + members = [m for m in m_list + if m['ip_address'] != fixed_ip] + pool_client.update_pool_with_members(lb_pool_id, members) + except nsxlib_exc.ManagerError: + self.lbv2_driver.member.failed_completion(context, member) + msg = _('Failed to remove member from pool on NSX backend') + raise n_exc.BadRequest(resource='lbaas-member', msg=msg) + self.lbv2_driver.member.successful_completion( + context, member, delete=True) diff --git a/vmware_nsx/services/lbaas/nsx_v3/pool_mgr.py b/vmware_nsx/services/lbaas/nsx_v3/pool_mgr.py new file mode 100644 index 0000000000..762e029ec0 --- /dev/null +++ b/vmware_nsx/services/lbaas/nsx_v3/pool_mgr.py @@ -0,0 +1,115 @@ +# Copyright 2017 VMware, Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib import exceptions as n_exc +from oslo_log import helpers as log_helpers +from oslo_log import log as logging +from oslo_utils import excutils + +from vmware_nsx._i18n import _ +from vmware_nsx.common import exceptions as nsx_exc +from vmware_nsx.db import db as nsx_db +from vmware_nsx.services.lbaas import base_mgr +from vmware_nsx.services.lbaas import lb_const +from vmware_nsx.services.lbaas.nsx_v3 import lb_utils +from vmware_nsxlib.v3 import exceptions as nsxlib_exc +from vmware_nsxlib.v3 import utils + +LOG = logging.getLogger(__name__) + + +class EdgePoolManager(base_mgr.Nsxv3LoadbalancerBaseManager): + @log_helpers.log_method_call + def __init__(self): + super(EdgePoolManager, self).__init__() + + @log_helpers.log_method_call + def create(self, context, pool): + listener_id = pool.listener.id + lb_id = pool.loadbalancer_id + pool_client = self.core_plugin.nsxlib.load_balancer.pool + vs_client = self.core_plugin.nsxlib.load_balancer.virtual_server + pool_name = utils.get_name_and_uuid(pool.name, pool.id) + tags = lb_utils.get_tags(self.core_plugin, pool.id, + lb_const.LB_POOL_TYPE, pool.tenant_id, + context.project_name) + try: + lb_pool = pool_client.create(display_name=pool_name, + tags=tags, + algorithm=pool.lb_algorithm) + except nsxlib_exc.ManagerError: + self.lbv2_driver.pool.failed_completion(context, pool) + msg = (_('Failed to create pool on NSX backend: %(pool)s') % + {'pool': pool.id}) + raise n_exc.BadRequest(resource='lbaas-pool', msg=msg) + + binding = nsx_db.get_nsx_lbaas_listener_binding( + context.session, lb_id, listener_id) + if binding: + vs_id = binding['lb_vs_id'] + try: + vs_client.update(vs_id, pool_id=lb_pool['id']) + except nsxlib_exc.ManagerError: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.pool.failed_completion(context, pool) + LOG.error('Failed to attach pool %s to virtual server %s', + lb_pool['id'], vs_id) + nsx_db.add_nsx_lbaas_pool_binding( + context.session, lb_id, pool.id, lb_pool['id'], vs_id) + else: + msg = (_("Couldn't find binding on the listener: %s") % + listener_id) + raise nsx_exc.NsxPluginException(err_msg=msg) + self.lbv2_driver.pool.successful_completion(context, pool) + + @log_helpers.log_method_call + def update(self, context, old_pool, new_pool): + try: + self.lbv2_driver.pool.successful_completion(context, new_pool) + except Exception: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.pool.failed_completion(context, new_pool) + + @log_helpers.log_method_call + def delete(self, context, pool): + lb_id = pool.loadbalancer_id + pool_client = self.core_plugin.nsxlib.load_balancer.pool + vs_client = self.core_plugin.nsxlib.load_balancer.virtual_server + + binding = nsx_db.get_nsx_lbaas_pool_binding( + context.session, lb_id, pool.id) + if binding: + vs_id = binding['lb_vs_id'] + lb_pool_id = binding['lb_pool_id'] + try: + vs_client.update(vs_id, pool_id='') + except nsxlib_exc.ManagerError: + self.lbv2_driver.pool.failed_completion(context, pool) + msg = _('Failed to remove lb pool %(pool)s from virtual ' + 'server %(vs)s') % {'pool': lb_pool_id, + 'vs': vs_id} + raise n_exc.BadRequest(resource='lbaas-pool', msg=msg) + try: + pool_client.delete(lb_pool_id) + except nsxlib_exc.ManagerError: + self.lbv2_driver.pool.failed_completion(context, pool) + msg = (_('Failed to delete lb pool from nsx: %(pool)s') % + {'pool': lb_pool_id}) + raise n_exc.BadRequest(resource='lbaas-pool', msg=msg) + nsx_db.delete_nsx_lbaas_pool_binding(context.session, + lb_id, pool.id) + + self.lbv2_driver.pool.successful_completion( + context, pool, delete=True) diff --git a/vmware_nsx/tests/unit/services/lbaas/__init__.py b/vmware_nsx/tests/unit/services/lbaas/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py b/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py new file mode 100644 index 0000000000..0a6c537572 --- /dev/null +++ b/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py @@ -0,0 +1,519 @@ +# Copyright (c) 2017 VMware, Inc. +# +# 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.tests import base +from neutron_lbaas.services.loadbalancer import data_models as lb_models +from neutron_lib import context + +from vmware_nsx.db import db as nsx_db +from vmware_nsx.services.lbaas import base_mgr +from vmware_nsx.services.lbaas.nsx_v3 import lb_driver_v2 +from vmware_nsx.services.lbaas.nsx_v3 import lb_utils + + +LB_VIP = '10.0.0.10' +LB_ROUTER_ID = 'router-x' +LB_ID = 'xxx-xxx' +LB_TENANT_ID = 'yyy-yyy' +LB_SERVICE_ID = 'service-1' +LB_BINDING = {'loadbalancer_id': LB_ID, + 'lb_service_id': LB_SERVICE_ID, + 'lb_router_id': LB_ROUTER_ID, + 'vip_address': LB_VIP} +LB_NETWORK = {'router:external': False, + 'id': 'xxxxx', + 'name': 'network-1'} +LISTENER_ID = 'listener-x' +APP_PROFILE_ID = 'appp-x' +LB_VS_ID = 'vs-x' +LB_APP_PROFILE = { + "resource_type": "LbHttpProfile", + "description": "my http profile", + "id": APP_PROFILE_ID, + "display_name": "httpprofile1", + "ntlm": False, + "request_header_size": 1024, + "http_redirect_to_https": False, + "idle_timeout": 1800, + "x_forwarded_for": "INSERT", +} +LISTENER_BINDING = {'loadbalancer_id': LB_ID, + 'listener_id': LISTENER_ID, + 'app_profile_id': APP_PROFILE_ID, + 'lb_vs_id': LB_VS_ID} +POOL_ID = 'ppp-qqq' +LB_POOL_ID = 'pool-xx' +LB_POOL = { + "display_name": "httppool1", + "description": "my http pool", + "id": LB_POOL_ID, + "algorithm": "ROUND_ROBIN", +} +POOL_BINDING = {'loadbalancer_id': LB_ID, + 'pool_id': POOL_ID, + 'lb_pool_id': LB_POOL_ID, + 'lb_vs_id': LB_VS_ID} +MEMBER_ID = 'mmm-mmm' +MEMBER_ADDRESS = '10.0.0.200' +LB_MEMBER = {'display_name': 'member-' + MEMBER_ID, + 'weight': 1, 'ip_address': MEMBER_ADDRESS, 'port': 80} +LB_POOL_WITH_MEMBER = { + "display_name": "httppool1", + "description": "my http pool", + "id": LB_POOL_ID, + "algorithm": "ROUND_ROBIN", + "members": [ + { + "display_name": "http-member1", + "ip_address": MEMBER_ADDRESS, + "port": "80", + "weight": "1", + "admin_state": "ENABLED" + } + ] + +} +HM_ID = 'hhh-mmm' +LB_MONITOR_ID = 'mmm-ddd' + +HM_BINDING = {'loadbalancer_id': LB_ID, + 'pool_id': POOL_ID, + 'hm_id': HM_ID, + 'lb_monitor_id': LB_MONITOR_ID, + 'lb_pool_id': LB_POOL_ID} + + +class BaseTestEdgeLbaasV2(base.BaseTestCase): + def _tested_entity(self): + return None + + def setUp(self): + super(BaseTestEdgeLbaasV2, self).setUp() + + self.context = context.get_admin_context() + self.edge_driver = lb_driver_v2.EdgeLoadbalancerDriverV2() + + self.lbv2_driver = mock.Mock() + self.core_plugin = mock.Mock() + base_mgr.LoadbalancerBaseManager._lbv2_driver = self.lbv2_driver + base_mgr.LoadbalancerBaseManager._core_plugin = self.core_plugin + self._patch_lb_plugin(self.lbv2_driver, self._tested_entity) + self._patch_nsxlib_lb_clients(self.core_plugin) + + self.lb = lb_models.LoadBalancer(LB_ID, LB_TENANT_ID, 'lb1', '', + 'some-subnet', 'port-id', LB_VIP) + self.listener = lb_models.Listener(LISTENER_ID, LB_TENANT_ID, + 'listener1', '', None, LB_ID, + 'HTTP', protocol_port=80, + loadbalancer=self.lb) + self.pool = lb_models.Pool(POOL_ID, LB_TENANT_ID, 'pool1', '', + None, 'HTTP', 'ROUND_ROBIN', + loadbalancer_id=LB_ID, + listener=self.listener, + listeners=[self.listener], + loadbalancer=self.lb) + self.member = lb_models.Member(MEMBER_ID, LB_TENANT_ID, POOL_ID, + MEMBER_ADDRESS, 80, 1, pool=self.pool, + name='member-mmm-mmm') + self.hm = lb_models.HealthMonitor(HM_ID, LB_TENANT_ID, 'PING', 3, 3, + 1, pool=self.pool, name='hm1') + + def tearDown(self): + self._unpatch_lb_plugin(self.lbv2_driver, self._tested_entity) + super(BaseTestEdgeLbaasV2, self).tearDown() + + def _patch_lb_plugin(self, lb_plugin, manager): + self.real_manager = getattr(lb_plugin, manager) + lb_manager = mock.patch.object(lb_plugin, manager).start() + mock.patch.object(lb_manager, 'create').start() + mock.patch.object(lb_manager, 'update').start() + mock.patch.object(lb_manager, 'delete').start() + mock.patch.object(lb_manager, 'successful_completion').start() + + def _patch_nsxlib_lb_clients(self, core_plugin): + nsxlib = mock.patch.object(core_plugin, 'nsxlib').start() + load_balancer = mock.patch.object(nsxlib, 'load_balancer').start() + self.service_client = mock.patch.object(load_balancer, + 'service').start() + self.app_client = mock.patch.object(load_balancer, + 'application_profile').start() + self.vs_client = mock.patch.object(load_balancer, + 'virtual_server').start() + self.pool_client = mock.patch.object(load_balancer, + 'pool').start() + self.monitor_client = mock.patch.object(load_balancer, + 'monitor').start() + + def _unpatch_lb_plugin(self, lb_plugin, manager): + setattr(lb_plugin, manager, self.real_manager) + + +class TestEdgeLbaasV2Loadbalancer(BaseTestEdgeLbaasV2): + def setUp(self): + super(TestEdgeLbaasV2Loadbalancer, self).setUp() + + @property + def _tested_entity(self): + return 'load_balancer' + + def test_create(self): + with mock.patch.object(lb_utils, 'validate_lb_subnet' + ) as mock_validate_lb_subnet: + mock_validate_lb_subnet.return_value = True + + self.edge_driver.loadbalancer.create(self.context, self.lb) + + mock_successful_completion = ( + self.lbv2_driver.load_balancer.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.lb) + + def test_update(self): + new_lb = lb_models.LoadBalancer(LB_ID, 'yyy-yyy', 'lb1-new', + 'new-description', 'some-subnet', + 'port-id', LB_VIP) + + self.edge_driver.loadbalancer.update(self.context, self.lb, new_lb) + + mock_successful_completion = ( + self.lbv2_driver.load_balancer.successful_completion) + mock_successful_completion.assert_called_with(self.context, new_lb) + + def test_delete(self): + self.edge_driver.loadbalancer.delete(self.context, self.lb) + + mock_successful_completion = ( + self.lbv2_driver.load_balancer.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.lb, + delete=True) + + def test_stats(self): + pass + + def test_refresh(self): + pass + + +class TestEdgeLbaasV2Listener(BaseTestEdgeLbaasV2): + def setUp(self): + super(TestEdgeLbaasV2Listener, self).setUp() + + @property + def _tested_entity(self): + return 'listener' + + def test_create(self): + with mock.patch.object(self.app_client, 'create' + ) as mock_create_app_profile, \ + mock.patch.object(self.vs_client, 'create' + ) as mock_create_virtual_server, \ + mock.patch.object(nsx_db, 'add_nsx_lbaas_listener_binding' + ) as mock_add_listener_binding: + mock_create_app_profile.return_value = {'id': APP_PROFILE_ID} + mock_create_virtual_server.return_value = {'id': LB_VS_ID} + + self.edge_driver.listener.create(self.context, self.listener) + + mock_add_listener_binding.assert_called_with( + self.context.session, LB_ID, LISTENER_ID, APP_PROFILE_ID, + LB_VS_ID) + + mock_successful_completion = ( + self.lbv2_driver.listener.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.listener) + + def test_update(self): + new_listener = lb_models.Listener(LISTENER_ID, LB_TENANT_ID, + 'listener1-new', 'new-description', + None, LB_ID, protocol_port=8000, + loadbalancer=self.lb) + + self.edge_driver.listener.update(self.context, self.listener, + new_listener) + + mock_successful_completion = ( + self.lbv2_driver.listener.successful_completion) + mock_successful_completion.assert_called_with(self.context, + new_listener) + + def test_delete(self): + with mock.patch.object(nsx_db, 'get_nsx_lbaas_listener_binding' + ) as mock_get_listener_binding, \ + mock.patch.object(self.app_client, 'delete' + ) as mock_delete_app_profile, \ + mock.patch.object(self.vs_client, 'delete' + ) as mock_delete_virtual_server, \ + mock.patch.object(nsx_db, 'delete_nsx_lbaas_listener_binding', + ) as mock_delete_listener_binding: + mock_get_listener_binding.return_value = LISTENER_BINDING + + self.edge_driver.listener.delete(self.context, self.listener) + + mock_delete_virtual_server.assert_called_with(LB_VS_ID) + mock_delete_app_profile.assert_called_with(APP_PROFILE_ID) + + mock_delete_listener_binding.assert_called_with( + self.context.session, LB_ID, LISTENER_ID) + + mock_successful_completion = ( + self.lbv2_driver.listener.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.listener, + delete=True) + + +class TestEdgeLbaasV2Pool(BaseTestEdgeLbaasV2): + def setUp(self): + super(TestEdgeLbaasV2Pool, self).setUp() + + @property + def _tested_entity(self): + return 'pool' + + def test_create(self): + with mock.patch.object(self.pool_client, 'create' + ) as mock_create_pool, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_listener_binding' + ) as mock_get_listener_binding, \ + mock.patch.object(self.vs_client, 'update', return_value=None), \ + mock.patch.object(nsx_db, 'add_nsx_lbaas_pool_binding' + ) as mock_add_pool_binding: + mock_create_pool.return_value = {'id': LB_POOL_ID} + mock_get_listener_binding.return_value = LISTENER_BINDING + + self.edge_driver.pool.create(self.context, self.pool) + + mock_add_pool_binding.assert_called_with( + self.context.session, LB_ID, POOL_ID, LB_POOL_ID, LB_VS_ID) + mock_successful_completion = ( + self.lbv2_driver.pool.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.pool) + + def test_update(self): + new_pool = lb_models.Pool(POOL_ID, LB_TENANT_ID, 'pool-name', '', + None, 'HTTP', 'LEAST_CONNECTIONS', + listener=self.listener) + self.edge_driver.pool.update(self.context, self.pool, new_pool) + + mock_successful_completion = ( + self.lbv2_driver.pool.successful_completion) + mock_successful_completion.assert_called_with(self.context, new_pool) + + def test_delete(self): + with mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(self.vs_client, 'update', return_value=None + ) as mock_update_virtual_server, \ + mock.patch.object(self.pool_client, 'delete' + ) as mock_delete_pool, \ + mock.patch.object(nsx_db, 'delete_nsx_lbaas_pool_binding' + ) as mock_delete_pool_binding: + mock_get_pool_binding.return_value = POOL_BINDING + + self.edge_driver.pool.delete(self.context, self.pool) + + mock_update_virtual_server.assert_called_with(LB_VS_ID, + pool_id='') + mock_delete_pool.assert_called_with(LB_POOL_ID) + mock_delete_pool_binding.assert_called_with( + self.context.session, LB_ID, POOL_ID) + + mock_successful_completion = ( + self.lbv2_driver.pool.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.pool, + delete=True) + + +class TestEdgeLbaasV2Member(BaseTestEdgeLbaasV2): + def setUp(self): + super(TestEdgeLbaasV2Member, self).setUp() + + @property + def _tested_entity(self): + return 'member' + + def test_create(self): + with mock.patch.object(lb_utils, 'validate_lb_subnet' + ) as mock_validate_lb_subnet, \ + mock.patch.object(self.lbv2_driver.plugin, 'get_pool_members' + ) as mock_get_pool_members, \ + mock.patch.object(lb_utils, 'get_network_from_subnet' + ) as mock_get_network, \ + mock.patch.object(lb_utils, 'get_router_from_network' + ) as mock_get_router, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(nsx_db, 'get_nsx_router_id' + ) as mock_get_nsx_router_id, \ + mock.patch.object(self.service_client, 'get_router_lb_service' + ) as mock_get_lb_service, \ + mock.patch.object(nsx_db, 'add_nsx_lbaas_loadbalancer_binding' + ) as mock_add_loadbalancer_bidning, \ + mock.patch.object(self.service_client, + 'add_virtual_server_to_service' + ) as mock_add_vs_to_service, \ + mock.patch.object(self.pool_client, 'get' + ) as mock_get_pool, \ + mock.patch.object(self.pool_client, 'update_pool_with_members' + ) as mock_update_pool_with_members: + mock_validate_lb_subnet.return_value = True + mock_get_pool_members.return_value = [self.member] + mock_get_network.return_value = LB_NETWORK + mock_get_router.return_value = LB_ROUTER_ID + mock_get_pool_binding.return_value = POOL_BINDING + mock_get_nsx_router_id.return_value = LB_ROUTER_ID + mock_get_lb_service.return_value = {'id': LB_SERVICE_ID} + mock_get_pool.return_value = LB_POOL + + self.edge_driver.member.create(self.context, self.member) + + mock_add_loadbalancer_bidning.assert_called_with( + self.context.session, LB_ID, LB_SERVICE_ID, LB_ROUTER_ID, + LB_VIP) + mock_add_vs_to_service.assert_called_with(LB_SERVICE_ID, LB_VS_ID) + mock_update_pool_with_members.assert_called_with(LB_POOL_ID, + [LB_MEMBER]) + mock_successful_completion = ( + self.lbv2_driver.member.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.member) + + def test_update(self): + new_member = lb_models.Member(MEMBER_ID, LB_TENANT_ID, POOL_ID, + MEMBER_ADDRESS, 80, 1, pool=self.pool, + name='member-nnn-nnn') + self.edge_driver.member.update(self.context, self.pool, new_member) + + mock_successful_completion = ( + self.lbv2_driver.member.successful_completion) + mock_successful_completion.assert_called_with(self.context, new_member) + + def test_delete(self): + with mock.patch.object(self.lbv2_driver.plugin, 'get_pool_members' + ) as mock_get_pool_members, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_loadbalancer_binding' + ) as mock_get_loadbalancer_binding, \ + mock.patch.object(self.service_client, 'get' + ) as mock_get_lb_service, \ + mock.patch.object(self.service_client, 'update' + ) as mock_update_lb_service, \ + mock.patch.object(self.service_client, 'delete' + ) as mock_delete_lb_service, \ + mock.patch.object(nsx_db, 'delete_nsx_lbaas_loadbalancer_binding' + ) as mock_delete_loadbalancer_binding, \ + mock.patch.object(self.pool_client, 'get' + ) as mock_get_pool, \ + mock.patch.object(lb_utils, 'get_network_from_subnet' + ) as mock_get_network_from_subnet, \ + mock.patch.object(self.pool_client, 'update_pool_with_members' + ) as mock_update_pool_with_members: + mock_get_pool_members.return_value = [self.member] + mock_get_pool_binding.return_value = POOL_BINDING + mock_get_loadbalancer_binding.return_value = LB_BINDING + mock_get_lb_service.return_value = { + 'id': LB_SERVICE_ID, + 'virtual_server_ids': [LB_VS_ID]} + mock_get_pool.return_value = LB_POOL_WITH_MEMBER + mock_get_network_from_subnet.return_value = LB_NETWORK + + self.edge_driver.member.delete(self.context, self.member) + + mock_update_lb_service.assert_called_with(LB_SERVICE_ID, + virtual_server_ids=[]) + mock_delete_lb_service.assert_called_with(LB_SERVICE_ID) + mock_delete_loadbalancer_binding.assert_called_with( + self.context.session, LB_ID) + mock_update_pool_with_members.assert_called_with(LB_POOL_ID, []) + + mock_successful_completion = ( + self.lbv2_driver.member.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.member, + delete=True) + + +class TestEdgeLbaasV2HealthMonitor(BaseTestEdgeLbaasV2): + def setUp(self): + super(TestEdgeLbaasV2HealthMonitor, self).setUp() + + @property + def _tested_entity(self): + return 'health_monitor' + + def test_create(self): + with mock.patch.object(self.monitor_client, 'create' + ) as mock_create_monitor, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(self.pool_client, 'add_monitor_to_pool' + ) as mock_add_monitor_to_pool, \ + mock.patch.object(nsx_db, 'add_nsx_lbaas_monitor_binding' + ) as mock_add_monitor_binding: + mock_create_monitor.return_value = {'id': LB_MONITOR_ID} + mock_get_pool_binding.return_value = POOL_BINDING + + self.edge_driver.healthmonitor.create(self.context, self.hm) + + mock_add_monitor_to_pool.assert_called_with(LB_POOL_ID, + LB_MONITOR_ID) + mock_add_monitor_binding.assert_called_with( + self.context.session, LB_ID, POOL_ID, HM_ID, LB_MONITOR_ID, + LB_POOL_ID) + + mock_successful_completion = ( + self.lbv2_driver.health_monitor.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.hm) + + def test_update(self): + new_hm = lb_models.HealthMonitor(HM_ID, LB_TENANT_ID, 'PING', 3, 3, + 3, pool=self.pool) + self.edge_driver.healthmonitor.update(self.context, self.pool, new_hm) + + mock_successful_completion = ( + self.lbv2_driver.health_monitor.successful_completion) + mock_successful_completion.assert_called_with(self.context, new_hm) + + def test_delete(self): + with mock.patch.object(nsx_db, 'get_nsx_lbaas_monitor_binding' + ) as mock_get_monitor_binding, \ + mock.patch.object(self.pool_client, 'remove_monitor_from_pool' + ) as mock_remove_monitor_from_pool, \ + mock.patch.object(self.monitor_client, 'delete' + ) as mock_delete_monitor, \ + mock.patch.object(nsx_db, 'delete_nsx_lbaas_monitor_binding' + ) as mock_delete_monitor_binding: + mock_get_monitor_binding.return_value = HM_BINDING + + self.edge_driver.healthmonitor.delete(self.context, self.hm) + + mock_remove_monitor_from_pool.assert_called_with(LB_POOL_ID, + LB_MONITOR_ID) + mock_delete_monitor.assert_called_with(LB_MONITOR_ID) + mock_delete_monitor_binding.assert_called_with( + self.context.session, LB_ID, POOL_ID, HM_ID) + + mock_successful_completion = ( + self.lbv2_driver.health_monitor.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.hm, + delete=True)