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 5e6990057d..1ef602f2b0 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -96,6 +96,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 @@ -187,6 +188,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( @@ -269,6 +271,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 @@ -2834,6 +2848,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)