From 410575e5e79cc3d2fc84ca883501971fcfd77ce3 Mon Sep 17 00:00:00 2001 From: Tong Liu Date: Wed, 25 Oct 2017 02:13:29 +0000 Subject: [PATCH] NSXv3: Refactor LBaaS L7 code Refactor L7 code to support multiple l7 rules in a policy, and fix the position of lb rule in the virtual server. Change-Id: I08e10ecc6ef594c5539fe7315ffa3da51b2184d8 --- vmware_nsx/db/db.py | 25 +- .../alembic_migrations/versions/CONTRACT_HEAD | 2 +- .../717f7f63a219_nsxv3_lbaas_l7policy.py | 62 +++++ vmware_nsx/db/nsx_models.py | 21 +- vmware_nsx/services/lbaas/lb_const.py | 2 +- .../services/lbaas/nsx_v3/l7policy_mgr.py | 133 ++++++++--- .../services/lbaas/nsx_v3/l7rule_mgr.py | 186 ++------------ vmware_nsx/services/lbaas/nsx_v3/lb_utils.py | 86 +++++++ .../unit/services/lbaas/test_nsxv3_driver.py | 226 +++++++++++++----- 9 files changed, 471 insertions(+), 272 deletions(-) create mode 100644 vmware_nsx/db/migration/alembic_migrations/versions/queens/contract/717f7f63a219_nsxv3_lbaas_l7policy.py diff --git a/vmware_nsx/db/db.py b/vmware_nsx/db/db.py index ff7fbd7244..84c9b95086 100644 --- a/vmware_nsx/db/db.py +++ b/vmware_nsx/db/db.py @@ -633,29 +633,22 @@ def delete_nsx_lbaas_monitor_binding(session, loadbalancer_id, pool_id, pool_id=pool_id, hm_id=hm_id).delete()) -def add_nsx_lbaas_l7rule_binding(session, loadbalancer_id, l7policy_id, - l7rule_id, lb_rule_id, lb_vs_id): +def add_nsx_lbaas_l7policy_binding(session, l7policy_id, lb_rule_id, lb_vs_id): with session.begin(subtransactions=True): - binding = nsx_models.NsxLbaasL7Rule( - loadbalancer_id=loadbalancer_id, l7policy_id=l7policy_id, - l7rule_id=l7rule_id, lb_rule_id=lb_rule_id, lb_vs_id=lb_vs_id) + binding = nsx_models.NsxLbaasL7Policy( + l7policy_id=l7policy_id, lb_rule_id=lb_rule_id, lb_vs_id=lb_vs_id) session.add(binding) return binding -def get_nsx_lbaas_l7rule_binding(session, loadbalancer_id, l7policy_id, - l7rule_id): +def get_nsx_lbaas_l7policy_binding(session, l7policy_id): try: - return session.query(nsx_models.NsxLbaasL7Rule).filter_by( - loadbalancer_id=loadbalancer_id, l7policy_id=l7policy_id, - l7rule_id=l7rule_id).one() + return session.query(nsx_models.NsxLbaasL7Policy).filter_by( + l7policy_id=l7policy_id).one() except exc.NoResultFound: return -def delete_nsx_lbaas_l7rule_binding(session, loadbalancer_id, l7policy_id, - l7rule_id): - return (session.query(nsx_models.NsxLbaasL7Rule). - filter_by(loadbalancer_id=loadbalancer_id, - l7policy_id=l7policy_id, - l7rule_id=l7rule_id).delete()) +def delete_nsx_lbaas_l7policy_binding(session, l7policy_id): + return (session.query(nsx_models.NsxLbaasL7Policy). + filter_by(l7policy_id=l7policy_id).delete()) diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/CONTRACT_HEAD b/vmware_nsx/db/migration/alembic_migrations/versions/CONTRACT_HEAD index 2da64c137b..aa9929d28d 100644 --- a/vmware_nsx/db/migration/alembic_migrations/versions/CONTRACT_HEAD +++ b/vmware_nsx/db/migration/alembic_migrations/versions/CONTRACT_HEAD @@ -1 +1 @@ -a1be06050b41 \ No newline at end of file +717f7f63a219 diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/queens/contract/717f7f63a219_nsxv3_lbaas_l7policy.py b/vmware_nsx/db/migration/alembic_migrations/versions/queens/contract/717f7f63a219_nsxv3_lbaas_l7policy.py new file mode 100644 index 0000000000..72cf9d4483 --- /dev/null +++ b/vmware_nsx/db/migration/alembic_migrations/versions/queens/contract/717f7f63a219_nsxv3_lbaas_l7policy.py @@ -0,0 +1,62 @@ +# 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. + +"""nsxv3_lbaas_l7policy + +Revision ID: 717f7f63a219 +Revises: a1be06050b41 +Create Date: 2017-10-26 08:32:40.846088 + +""" + +# revision identifiers, used by Alembic. +revision = '717f7f63a219' +down_revision = 'a1be06050b41' + +from alembic import op +import sqlalchemy as sa + +from neutron.db import migration + + +def upgrade(): + + if migration.schema_has_table('nsxv3_lbaas_l7rules'): + op.drop_constraint('fk_nsxv3_lbaas_l7rules_id', 'nsxv3_lbaas_l7rules', + 'foreignkey') + op.drop_constraint('l7rule_id', 'nsxv3_lbaas_l7rules', 'primary') + op.drop_column('nsxv3_lbaas_l7rules', 'loadbalancer_id') + op.drop_column('nsxv3_lbaas_l7rules', 'l7rule_id') + op.rename_table('nsxv3_lbaas_l7rules', 'nsxv3_lbaas_l7policies') + + if migration.schema_has_table('lbaas_l7policies'): + op.create_foreign_key( + 'fk_nsxv3_lbaas_l7policies_id', 'nsxv3_lbaas_l7policies', + 'lbaas_l7policies', ['l7policy_id'], ['id'], + ondelete='CASCADE') + else: + op.create_table( + 'nsxv3_lbaas_l7policies', + sa.Column('l7policy_id', sa.String(36), nullable=False), + sa.Column('lb_rule_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('l7policy_id')) + + if migration.schema_has_table('lbaas_l7policies'): + op.create_foreign_key( + 'fk_nsxv3_lbaas_l7policies_id', 'nsxv3_lbaas_l7policies', + 'lbaas_l7policies', ['l7policy_id'], ['id'], + ondelete='CASCADE') diff --git a/vmware_nsx/db/nsx_models.py b/vmware_nsx/db/nsx_models.py index a9dfff7616..76d7b1357b 100644 --- a/vmware_nsx/db/nsx_models.py +++ b/vmware_nsx/db/nsx_models.py @@ -451,7 +451,14 @@ class NsxLbaasMonitor(model_base.BASEV2, models.TimestampMixin): class NsxLbaasL7Rule(model_base.BASEV2, models.TimestampMixin): - """Stores the mapping between LBaaS monitor and NSX LB monitor""" + """Stores the mapping between LBaaS monitor and NSX LB monitor + + This table is only used in Pike and obsoleted since Queen as the + mapping has been stored in nsxv3_lbaas_l7policies table instead. + This original table was added in pike so that we cannot change + DB migration script there, but instead we update the table with + a new db migration script in Queen. + """ __tablename__ = 'nsxv3_lbaas_l7rules' loadbalancer_id = sa.Column(sa.String(36), primary_key=True) l7policy_id = sa.Column(sa.String(36), primary_key=True) @@ -462,3 +469,15 @@ class NsxLbaasL7Rule(model_base.BASEV2, models.TimestampMixin): primary_key=True) lb_rule_id = sa.Column(sa.String(36), nullable=False) lb_vs_id = sa.Column(sa.String(36), nullable=False) + + +class NsxLbaasL7Policy(model_base.BASEV2, models.TimestampMixin): + """Stores the mapping between LBaaS l7policy and NSX LB rule""" + __tablename__ = 'nsxv3_lbaas_l7policies' + l7policy_id = sa.Column(sa.String(36), + sa.ForeignKey('lbaas_l7policies.id', + name='fk_nsxv3_lbaas_l7policies_id', + ondelete="CASCADE"), + primary_key=True) + lb_rule_id = sa.Column(sa.String(36), nullable=False) + lb_vs_id = sa.Column(sa.String(36), nullable=False) diff --git a/vmware_nsx/services/lbaas/lb_const.py b/vmware_nsx/services/lbaas/lb_const.py index fe9bd41f4d..1bce62b5ae 100644 --- a/vmware_nsx/services/lbaas/lb_const.py +++ b/vmware_nsx/services/lbaas/lb_const.py @@ -79,7 +79,7 @@ LB_LB_NAME = 'os-lbaas-lb-name' LB_LISTENER_TYPE = 'os-lbaas-listener-id' LB_HM_TYPE = 'os-lbaas-hm-id' LB_POOL_TYPE = 'os-lbaas-pool-id' -LB_L7RULE_TYPE = 'os-lbaas-l7rule-id' +LB_L7POLICY_TYPE = 'os-lbaas-l7policy-id' LB_HTTP_PROFILE = 'LbHttpProfile' LB_TCP_PROFILE = 'LbFastTcpProfile' LB_UDP_PROFILE = 'LbFastUdpProfile' diff --git a/vmware_nsx/services/lbaas/nsx_v3/l7policy_mgr.py b/vmware_nsx/services/lbaas/nsx_v3/l7policy_mgr.py index 9f588ae465..aee67802bd 100644 --- a/vmware_nsx/services/lbaas/nsx_v3/l7policy_mgr.py +++ b/vmware_nsx/services/lbaas/nsx_v3/l7policy_mgr.py @@ -16,10 +16,13 @@ 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 LOG = logging.getLogger(__name__) @@ -31,51 +34,111 @@ class EdgeL7PolicyManager(base_mgr.Nsxv3LoadbalancerBaseManager): super(EdgeL7PolicyManager, self).__init__() @log_helpers.log_method_call - def _l7policy_action(self, context, policy, action, delete=False): - try: - self.lbv2_driver.l7policy.successful_completion( - context, policy, delete=delete) - except Exception as e: - self.lbv2_driver.l7policy.failed_completion(context, policy) - msg = (_('Failed to %(action)s l7policy %(err)s') % - {'action': action, 'err': e}) - resource = 'lbaas-l7policy-%s' % action - raise n_exc.BadRequest(resource=resource, msg=msg) + def _update_policy_position(self, vs_id, rule_id, position): + vs_client = self.core_plugin.nsxlib.load_balancer.virtual_server + vs = vs_client.get(vs_id) + lb_rules = vs.get('rule_ids', []) + if rule_id in lb_rules: + lb_rules.remove(rule_id) + if len(lb_rules) < position: + lb_rules.append(rule_id) + else: + lb_rules.insert(position - 1, rule_id) + vs_client.update(vs_id, rule_ids=lb_rules) @log_helpers.log_method_call def create(self, context, policy): - self._l7policy_action(context, policy, 'create') + lb_id = policy.listener.loadbalancer_id + listener_id = policy.listener_id + rule_client = self.core_plugin.nsxlib.load_balancer.rule + tags = lb_utils.get_tags(self.core_plugin, policy.id, + lb_const.LB_L7POLICY_TYPE, + policy.tenant_id, context.project_name) + + binding = nsx_db.get_nsx_lbaas_listener_binding( + context.session, lb_id, listener_id) + if not binding: + self.lbv2_driver.l7policy.failed_completion(context, policy) + msg = _('Cannot find nsx lbaas binding for listener ' + '%(listener_id)s') % {'listener_id': listener_id} + raise n_exc.BadRequest(resource='lbaas-l7policy-create', msg=msg) + + vs_id = binding['lb_vs_id'] + rule_body = lb_utils.convert_l7policy_to_lb_rule(context, policy) + try: + lb_rule = rule_client.create(tags=tags, **rule_body) + except nsxlib_exc.ManagerError: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.l7policy.failed_completion(context, policy) + LOG.error('Failed to create lb rule at NSX backend') + try: + self._update_policy_position(vs_id, lb_rule['id'], + policy.position) + except nsxlib_exc.ManagerError: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.l7policy.failed_completion(context, policy) + LOG.error('Failed to add rule %(rule)% to virtual server ' + '%(vs)s at NSX backend', {'rule': lb_rule['id'], + 'vs': vs_id}) + + nsx_db.add_nsx_lbaas_l7policy_binding( + context.session, policy.id, lb_rule['id'], vs_id) + self.lbv2_driver.l7policy.successful_completion(context, policy) @log_helpers.log_method_call def update(self, context, old_policy, new_policy): - self._l7policy_action(context, new_policy, 'update') + rule_client = self.core_plugin.nsxlib.load_balancer.rule + binding = nsx_db.get_nsx_lbaas_l7policy_binding(context.session, + old_policy.id) + if not binding: + self.lbv2_driver.l7rule.failed_completion(context, new_policy) + msg = _('Cannot find nsx lbaas binding for policy ' + '%(policy_id)s') % {'policy_id': old_policy.id} + raise n_exc.BadRequest(resource='lbaas-l7policy-update', msg=msg) + + vs_id = binding['lb_vs_id'] + lb_rule_id = binding['lb_rule_id'] + rule_body = lb_utils.convert_l7policy_to_lb_rule(context, new_policy) + try: + rule_client.update(lb_rule_id, **rule_body) + if new_policy.position != old_policy.position: + self._update_policy_position(vs_id, lb_rule_id, + new_policy.position) + + except Exception as e: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.l7policy.failed_completion(context, + new_policy) + LOG.error('Failed to update L7policy %(policy)s: ' + '%(err)s', {'policy': old_policy.id, 'err': e}) + + self.lbv2_driver.l7policy.successful_completion(context, new_policy) @log_helpers.log_method_call def delete(self, context, policy): - lb_id = policy.listener.loadbalancer_id vs_client = self.core_plugin.nsxlib.load_balancer.virtual_server rule_client = self.core_plugin.nsxlib.load_balancer.rule - for rule in policy.rules: - binding = nsx_db.get_nsx_lbaas_l7rule_binding( - context.session, lb_id, policy.id, rule.id) - if binding: - vs_id = binding['lb_vs_id'] - rule_id = binding['lb_rule_id'] - try: - # Update virtual server to remove lb rule - vs_client.remove_rule(vs_id, rule_id) - rule_client.delete(rule_id) - except nsxlib_exc.ResourceNotFound: - LOG.warning('LB rule %(rule)s is not found on NSX', - {'rule': rule_id}) - except nsxlib_exc.ManagerError: - self.lbv2_driver.l7policy.failed_completion( - context, policy) - msg = (_('Failed to delete lb rule: %(rule)s') % - {'rule': rule.id}) - raise n_exc.BadRequest(resource='lbaas-l7rule-delete', - msg=msg) - nsx_db.delete_nsx_lbaas_l7rule_binding( - context.session, lb_id, policy.id, rule.id) + binding = nsx_db.get_nsx_lbaas_l7policy_binding(context.session, + policy.id) + if binding: + vs_id = binding['lb_vs_id'] + rule_id = binding['lb_rule_id'] + try: + # Update virtual server to remove lb rule + vs_client.remove_rule(vs_id, rule_id) + rule_client.delete(rule_id) + except nsxlib_exc.ResourceNotFound: + LOG.warning('LB rule %(rule)s is not found on NSX', + {'rule': rule_id}) + except nsxlib_exc.ManagerError: + self.lbv2_driver.l7policy.failed_completion( + context, policy) + msg = (_('Failed to delete lb rule: %(rule)s') % + {'rule': rule_id}) + raise n_exc.BadRequest(resource='lbaas-l7policy-delete', + msg=msg) + nsx_db.delete_nsx_lbaas_l7policy_binding( + context.session, policy.id) + self.lbv2_driver.l7policy.successful_completion( context, policy, delete=True) diff --git a/vmware_nsx/services/lbaas/nsx_v3/l7rule_mgr.py b/vmware_nsx/services/lbaas/nsx_v3/l7rule_mgr.py index 10800d99e0..e93f352b81 100644 --- a/vmware_nsx/services/lbaas/nsx_v3/l7rule_mgr.py +++ b/vmware_nsx/services/lbaas/nsx_v3/l7rule_mgr.py @@ -16,14 +16,12 @@ 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 LOG = logging.getLogger(__name__) @@ -33,171 +31,39 @@ class EdgeL7RuleManager(base_mgr.Nsxv3LoadbalancerBaseManager): def __init__(self): super(EdgeL7RuleManager, self).__init__() - @log_helpers.log_method_call - def _get_rule_match_conditions(self, rule): - match_conditions = [] - # values in rule have already been validated in LBaaS API, - # we won't need to valid anymore in driver, and just get - # the LB rule mapping from the dict. - match_type = lb_const.LB_RULE_MATCH_TYPE[rule.compare_type] - if rule.type == lb_const.L7_RULE_TYPE_COOKIE: - header_value = rule.key + '=' + rule.value - match_conditions.append( - {'type': 'LbHttpRequestHeaderCondition', - 'match_type': match_type, - 'header_name': 'Cookie', - 'header_value': header_value}) - elif rule.type == lb_const.L7_RULE_TYPE_FILE_TYPE: - match_conditions.append( - {'type': 'LbHttpRequestUriCondition', - 'match_type': match_type, - 'uri': '*.' + rule.value}) - elif rule.type == lb_const.L7_RULE_TYPE_HEADER: - match_conditions.append( - {'type': 'LbHttpRequestHeaderCondition', - 'match_type': match_type, - 'header_name': rule.key, - 'header_value': rule.value}) - elif rule.type == lb_const.L7_RULE_TYPE_HOST_NAME: - match_conditions.append( - {'type': 'LbHttpRequestHeaderCondition', - 'match_type': match_type, - 'header_name': 'Host', - 'header_value': rule.value}) - elif rule.type == lb_const.L7_RULE_TYPE_PATH: - match_conditions.append( - {'type': 'LbHttpRequestUriCondition', - 'match_type': match_type, - 'uri': rule.value}) - else: - msg = (_('l7rule type %(type)s is not supported in LBaaS') % - {'type': rule.type}) - LOG.error(msg) - raise n_exc.BadRequest(resource='lbaas-l7rule', msg=msg) - return match_conditions + def _update_l7rule_change(self, context, rule, delete=False): + rule_client = self.core_plugin.nsxlib.load_balancer.rule + binding = nsx_db.get_nsx_lbaas_l7policy_binding(context.session, + rule.policy.id) + if not binding: + self.lbv2_driver.l7rule.failed_completion(context, rule) + msg = _('Cannot find nsx lbaas binding for policy ' + '%(policy_id)s') % {'policy_id': rule.policy.id} + raise n_exc.BadRequest(resource='lbaas-l7policy-update', msg=msg) - @log_helpers.log_method_call - def _get_rule_actions(self, context, rule): - lb_id = rule.policy.listener.loadbalancer_id - l7policy = rule.policy - if l7policy.action == lb_const.L7_POLICY_ACTION_REDIRECT_TO_POOL: - pool_binding = nsx_db.get_nsx_lbaas_pool_binding( - context.session, lb_id, l7policy.redirect_pool_id) - if pool_binding: - lb_pool_id = pool_binding['lb_pool_id'] - actions = [{'type': lb_const.LB_SELECT_POOL_ACTION, - 'pool_id': lb_pool_id}] - else: - msg = _('Failed to get LB pool binding from nsx db') - raise n_exc.BadRequest(resource='lbaas-l7rule-create', - msg=msg) - elif l7policy.action == lb_const.L7_POLICY_ACTION_REDIRECT_TO_URL: - actions = [{'type': lb_const.LB_HTTP_REDIRECT_ACTION, - 'redirect_status': lb_const.LB_HTTP_REDIRECT_STATUS, - 'redirect_url': l7policy.redirect_url}] - elif l7policy.action == lb_const.L7_POLICY_ACTION_REJECT: - actions = [{'type': lb_const.LB_REJECT_ACTION, - 'reply_status': lb_const.LB_HTTP_REJECT_STATUS}] - else: - msg = (_('Invalid l7policy action: %(action)s') % - {'action': l7policy.action}) - raise n_exc.BadRequest(resource='lbaas-l7rule-create', - msg=msg) - return actions + lb_rule_id = binding['lb_rule_id'] + if delete: + lb_utils.remove_rule_from_policy(rule) + rule_body = lb_utils.convert_l7policy_to_lb_rule(context, rule.policy) + try: + rule_client.update(lb_rule_id, **rule_body) + except Exception as e: + with excutils.save_and_reraise_exception(): + self.lbv2_driver.l7rule.failed_completion(context, rule) + LOG.error('Failed to update L7policy %(policy)s: ' + '%(err)s', {'policy': rule.policy.id, 'err': e}) - @log_helpers.log_method_call - def _convert_l7policy_to_lb_rule(self, context, rule): - body = {} - body['match_conditions'] = self._get_rule_match_conditions(rule) - body['actions'] = self._get_rule_actions(context, rule) - body['phase'] = lb_const.LB_RULE_HTTP_FORWARDING - body['match_strategy'] = 'ANY' - - return body + self.lbv2_driver.l7rule.successful_completion(context, rule, + delete=delete) @log_helpers.log_method_call def create(self, context, rule): - lb_id = rule.policy.listener.loadbalancer_id - listener_id = rule.policy.listener_id - vs_client = self.core_plugin.nsxlib.load_balancer.virtual_server - rule_client = self.core_plugin.nsxlib.load_balancer.rule - tags = lb_utils.get_tags(self.core_plugin, rule.id, - lb_const.LB_L7RULE_TYPE, - rule.tenant_id, context.project_name) - - binding = nsx_db.get_nsx_lbaas_listener_binding( - context.session, lb_id, listener_id) - if not binding: - msg = _('Cannot find nsx lbaas binding for listener ' - '%(listener_id)s') % {'listener_id': listener_id} - raise n_exc.BadRequest(resource='lbaas-l7rule-create', msg=msg) - - vs_id = binding['lb_vs_id'] - rule_body = self._convert_l7policy_to_lb_rule(context, rule) - try: - lb_rule = rule_client.create(tags=tags, **rule_body) - except nsxlib_exc.ManagerError: - self.lbv2_driver.l7rule.failed_completion(context, rule) - msg = _('Failed to create lb rule at NSX backend') - raise n_exc.BadRequest(resource='lbaas-l7rule-create', - msg=msg) - try: - vs_client.add_rule(vs_id, lb_rule['id']) - except nsxlib_exc.ManagerError: - self.lbv2_driver.l7rule.failed_completion(context, rule) - msg = (_('Failed to add rule %(rule)s to virtual server ' - '%(vs)s at NSX backend') % - {'rule': lb_rule['id'], 'vs': vs_id}) - raise n_exc.BadRequest(resource='lbaas-l7rule-create', - msg=msg) - - nsx_db.add_nsx_lbaas_l7rule_binding( - context.session, lb_id, rule.l7policy_id, rule.id, - lb_rule['id'], vs_id) - self.lbv2_driver.l7rule.successful_completion(context, rule) + self._update_l7rule_change(context, rule) @log_helpers.log_method_call def update(self, context, old_rule, new_rule): - self.lbv2_driver.l7rule.successful_completion(context, new_rule) + self._update_l7rule_change(context, new_rule) @log_helpers.log_method_call def delete(self, context, rule): - lb_id = rule.policy.listener.loadbalancer_id - vs_client = self.core_plugin.nsxlib.load_balancer.virtual_server - rule_client = self.core_plugin.nsxlib.load_balancer.rule - - binding = nsx_db.get_nsx_lbaas_l7rule_binding( - context.session, lb_id, rule.l7policy_id, rule.id) - if binding: - vs_id = binding['lb_vs_id'] - rule_id = binding['lb_rule_id'] - try: - vs_client.remove_rule(vs_id, rule_id) - except nsx_exc.NsxResourceNotFound: - msg = (_("virtual server cannot be found on nsx: %(vs)s") % - {'vs': vs_id}) - raise n_exc.BadRequest(resource='lbaas-l7rule-delete', - msg=msg) - except nsxlib_exc.ManagerError: - self.lbv2_driver.l7rule.failed_completion(context, - rule) - msg = (_('Failed to update rule %(rule)s on virtual server ' - '%(vs)s') % {'rule': rule_id, 'vs': vs_id}) - raise n_exc.BadRequest(resource='lbaas-l7rule-delete', - msg=msg) - try: - rule_client.delete(rule_id) - except nsx_exc.NsxResourceNotFound: - LOG.warning("LB rule cannot be found on nsx: %(rule)s", - {'rule': rule_id}) - except nsxlib_exc.ManagerError: - self.lbv2_driver.l7rule.failed_completion(context, - rule) - msg = (_('Failed to delete lb rule: %(rule)s') % - {'rule': rule.id}) - raise n_exc.BadRequest(resource='lbaas-l7rule-delete', - msg=msg) - nsx_db.delete_nsx_lbaas_l7rule_binding( - context.session, lb_id, rule.l7policy_id, rule.id) - self.lbv2_driver.l7rule.successful_completion(context, rule, - delete=True) + self._update_l7rule_change(context, rule, delete=True) diff --git a/vmware_nsx/services/lbaas/nsx_v3/lb_utils.py b/vmware_nsx/services/lbaas/nsx_v3/lb_utils.py index fe9a0c2e40..a0e8e0a3ae 100644 --- a/vmware_nsx/services/lbaas/nsx_v3/lb_utils.py +++ b/vmware_nsx/services/lbaas/nsx_v3/lb_utils.py @@ -18,6 +18,7 @@ from neutron.services.flavors import flavors_plugin from neutron_lib import exceptions as n_exc from vmware_nsx._i18n import _ +from vmware_nsx.db import db as nsx_db from vmware_nsx.services.lbaas import lb_const from vmware_nsxlib.v3 import utils @@ -92,3 +93,88 @@ def validate_lb_subnet(context, plugin, subnet_id): return True else: return False + + +def get_rule_match_conditions(policy): + match_conditions = [] + # values in rule have already been validated in LBaaS API, + # we won't need to valid anymore in driver, and just get + # the LB rule mapping from the dict. + for rule in policy.rules: + match_type = lb_const.LB_RULE_MATCH_TYPE[rule.compare_type] + if rule.type == lb_const.L7_RULE_TYPE_COOKIE: + header_value = rule.key + '=' + rule.value + match_conditions.append( + {'type': 'LbHttpRequestHeaderCondition', + 'match_type': match_type, + 'header_name': 'Cookie', + 'header_value': header_value}) + elif rule.type == lb_const.L7_RULE_TYPE_FILE_TYPE: + match_conditions.append( + {'type': 'LbHttpRequestUriCondition', + 'match_type': match_type, + 'uri': '*.' + rule.value}) + elif rule.type == lb_const.L7_RULE_TYPE_HEADER: + match_conditions.append( + {'type': 'LbHttpRequestHeaderCondition', + 'match_type': match_type, + 'header_name': rule.key, + 'header_value': rule.value}) + elif rule.type == lb_const.L7_RULE_TYPE_HOST_NAME: + match_conditions.append( + {'type': 'LbHttpRequestHeaderCondition', + 'match_type': match_type, + 'header_name': 'Host', + 'header_value': rule.value}) + elif rule.type == lb_const.L7_RULE_TYPE_PATH: + match_conditions.append( + {'type': 'LbHttpRequestUriCondition', + 'match_type': match_type, + 'uri': rule.value}) + else: + msg = (_('l7rule type %(type)s is not supported in LBaaS') % + {'type': rule.type}) + raise n_exc.BadRequest(resource='lbaas-l7rule', msg=msg) + return match_conditions + + +def get_rule_actions(context, l7policy): + lb_id = l7policy.listener.loadbalancer_id + if l7policy.action == lb_const.L7_POLICY_ACTION_REDIRECT_TO_POOL: + pool_binding = nsx_db.get_nsx_lbaas_pool_binding( + context.session, lb_id, l7policy.redirect_pool_id) + if pool_binding: + lb_pool_id = pool_binding['lb_pool_id'] + actions = [{'type': lb_const.LB_SELECT_POOL_ACTION, + 'pool_id': lb_pool_id}] + else: + msg = _('Failed to get LB pool binding from nsx db') + raise n_exc.BadRequest(resource='lbaas-l7rule-create', + msg=msg) + elif l7policy.action == lb_const.L7_POLICY_ACTION_REDIRECT_TO_URL: + actions = [{'type': lb_const.LB_HTTP_REDIRECT_ACTION, + 'redirect_status': lb_const.LB_HTTP_REDIRECT_STATUS, + 'redirect_url': l7policy.redirect_url}] + elif l7policy.action == lb_const.L7_POLICY_ACTION_REJECT: + actions = [{'type': lb_const.LB_REJECT_ACTION, + 'reply_status': lb_const.LB_HTTP_REJECT_STATUS}] + else: + msg = (_('Invalid l7policy action: %(action)s') % + {'action': l7policy.action}) + raise n_exc.BadRequest(resource='lbaas-l7rule-create', + msg=msg) + return actions + + +def convert_l7policy_to_lb_rule(context, policy): + return { + 'match_conditions': get_rule_match_conditions(policy), + 'actions': get_rule_actions(context, policy), + 'phase': lb_const.LB_RULE_HTTP_FORWARDING, + 'match_strategy': 'ALL' + } + + +def remove_rule_from_policy(rule): + l7rules = rule.policy.rules + rule.policy.rules = [r for r in l7rules if r.id != rule.id] diff --git a/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py b/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py index f754697fca..2c486e50f8 100644 --- a/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py +++ b/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py @@ -98,11 +98,9 @@ HM_BINDING = {'loadbalancer_id': LB_ID, L7POLICY_ID = 'l7policy-xxx' LB_RULE_ID = 'lb-rule-xx' L7RULE_ID = 'l7rule-111' -L7RULE_BINDING = {'loadbalancer_id': LB_ID, - 'policy_id': L7POLICY_ID, - 'rule_id': L7RULE_ID, - 'lb_vs_id': LB_VS_ID, - 'lb_rule_id': LB_RULE_ID} +L7POLICY_BINDING = {'l7policy_id': L7POLICY_ID, + 'lb_vs_id': LB_VS_ID, + 'lb_rule_id': LB_RULE_ID} FAKE_CERT = {'id': 'cert-xyz'} @@ -152,7 +150,7 @@ class BaseTestEdgeLbaasV2(base.BaseTestCase): description='policy-desc', listener_id=LISTENER_ID, action='REDIRECT_TO_POOL', - redirect_pool_id=LB_POOL_ID, + redirect_pool_id=POOL_ID, listener=self.listener, position=1) self.l7rule = lb_models.L7Rule(L7RULE_ID, LB_TENANT_ID, @@ -660,11 +658,33 @@ class TestEdgeLbaasV2L7Policy(BaseTestEdgeLbaasV2): return 'l7policy' def test_create(self): - self.edge_driver.l7policy.create(self.context, self.l7policy) - mock_successful_completion = ( - self.lbv2_driver.l7policy.successful_completion) - mock_successful_completion.assert_called_with( - self.context, self.l7policy, delete=False) + with mock.patch.object(nsx_db, 'get_nsx_lbaas_listener_binding' + ) as mock_get_listener_binding, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(self.rule_client, 'create' + ) as mock_create_rule, \ + mock.patch.object(self.vs_client, 'get' + ) as mock_get_virtual_server, \ + mock.patch.object(self.vs_client, 'update' + ) as mock_update_virtual_server, \ + mock.patch.object(nsx_db, 'add_nsx_lbaas_l7policy_binding' + ) as mock_add_l7policy_binding: + mock_get_listener_binding.return_value = LISTENER_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + mock_create_rule.return_value = {'id': LB_RULE_ID} + mock_get_virtual_server.return_value = {'id': LB_VS_ID} + + self.edge_driver.l7policy.create(self.context, self.l7policy) + + mock_update_virtual_server.assert_called_with( + LB_VS_ID, rule_ids=[LB_RULE_ID]) + mock_add_l7policy_binding.assert_called_with( + self.context.session, L7POLICY_ID, LB_RULE_ID, LB_VS_ID) + mock_successful_completion = ( + self.lbv2_driver.l7policy.successful_completion) + mock_successful_completion.assert_called_with(self.context, + self.l7policy) def test_update(self): new_l7policy = lb_models.L7Policy(L7POLICY_ID, LB_TENANT_ID, @@ -672,20 +692,66 @@ class TestEdgeLbaasV2L7Policy(BaseTestEdgeLbaasV2): listener_id=LISTENER_ID, action='REJECT', listener=self.listener, - position=1) - self.edge_driver.l7policy.update(self.context, self.l7policy, - new_l7policy) - mock_successful_completion = ( - self.lbv2_driver.l7policy.successful_completion) - mock_successful_completion.assert_called_with( - self.context, new_l7policy, delete=False) + position=2) + vs_with_rules = { + 'id': LB_VS_ID, + 'rule_ids': [LB_RULE_ID, 'abc', 'xyz'] + } + rule_body = { + 'match_conditions': [], + 'actions': [{ + 'type': 'LbHttpRejectAction', + 'reply_status': '403'}], + 'phase': 'HTTP_FORWARDING', + 'match_strategy': 'ALL' + } + with mock.patch.object(nsx_db, 'get_nsx_lbaas_l7policy_binding' + ) as mock_get_l7policy_binding, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(self.rule_client, 'update' + ) as mock_update_rule, \ + mock.patch.object(self.vs_client, 'get' + ) as mock_get_virtual_server, \ + mock.patch.object(self.vs_client, 'update' + ) as mock_update_virtual_server: + mock_get_l7policy_binding.return_value = L7POLICY_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + mock_get_virtual_server.return_value = vs_with_rules + + self.edge_driver.l7policy.update(self.context, self.l7policy, + new_l7policy) + + mock_update_rule.assert_called_with(LB_RULE_ID, + **rule_body) + mock_update_virtual_server.assert_called_with( + LB_VS_ID, rule_ids=['abc', LB_RULE_ID, 'xyz']) + mock_successful_completion = ( + self.lbv2_driver.l7policy.successful_completion) + mock_successful_completion.assert_called_with(self.context, + new_l7policy) def test_delete(self): - self.edge_driver.l7policy.delete(self.context, self.l7policy) - mock_successful_completion = ( - self.lbv2_driver.l7policy.successful_completion) - mock_successful_completion.assert_called_with( - self.context, self.l7policy, delete=True) + with mock.patch.object(nsx_db, 'get_nsx_lbaas_l7policy_binding' + ) as mock_get_l7policy_binding, \ + mock.patch.object(self.vs_client, 'remove_rule' + ) as mock_vs_remove_rule, \ + mock.patch.object(self.rule_client, 'delete' + ) as mock_delete_rule, \ + mock.patch.object(nsx_db, 'delete_nsx_lbaas_l7policy_binding' + ) as mock_delete_l7policy_binding: + mock_get_l7policy_binding.return_value = L7POLICY_BINDING + + self.edge_driver.l7policy.delete(self.context, self.l7policy) + + mock_vs_remove_rule.assert_called_with(LB_VS_ID, LB_RULE_ID) + mock_delete_rule.assert_called_with(LB_RULE_ID) + mock_delete_l7policy_binding.assert_called_with( + self.context.session, L7POLICY_ID) + mock_successful_completion = ( + self.lbv2_driver.l7policy.successful_completion) + mock_successful_completion.assert_called_with( + self.context, self.l7policy, delete=True) class TestEdgeLbaasV2L7Rule(BaseTestEdgeLbaasV2): @@ -697,31 +763,39 @@ class TestEdgeLbaasV2L7Rule(BaseTestEdgeLbaasV2): return 'l7rule' def test_create(self): - with mock.patch.object(nsx_db, 'get_nsx_lbaas_listener_binding', - ) as mock_get_listnener_binding, \ - mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding', + self.l7policy.rules = [self.l7rule] + create_rule_body = { + 'match_conditions': [{ + 'type': 'LbHttpRequestHeaderCondition', + 'match_type': 'EQUALS', + 'header_name': self.l7rule.key, + 'header_value': self.l7rule.value}], + 'actions': [{ + 'type': 'LbSelectPoolAction', + 'pool_id': LB_POOL_ID}], + 'phase': 'HTTP_FORWARDING', + 'match_strategy': 'ALL' + } + + with mock.patch.object(nsx_db, 'get_nsx_lbaas_l7policy_binding' + ) as mock_get_l7policy_binding, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' ) as mock_get_pool_binding, \ - mock.patch.object(self.rule_client, 'create', - ) as mock_create_rule, \ - mock.patch.object(self.vs_client, 'add_rule', - ) as mock_add_rule, \ - mock.patch.object(nsx_db, 'add_nsx_lbaas_l7rule_binding', - ) as mock_add_l7rule_binding: - mock_get_listnener_binding.return_value = LISTENER_BINDING + mock.patch.object(self.rule_client, 'update' + ) as mock_update_rule: + mock_get_l7policy_binding.return_value = L7POLICY_BINDING mock_get_pool_binding.return_value = POOL_BINDING - mock_create_rule.return_value = {'id': LB_RULE_ID} self.edge_driver.l7rule.create(self.context, self.l7rule) - mock_add_rule.assert_called_with(LB_VS_ID, LB_RULE_ID) - mock_add_l7rule_binding.assert_called_with( - self.context.session, LB_ID, L7POLICY_ID, L7RULE_ID, - LB_RULE_ID, LB_VS_ID) + mock_update_rule.assert_called_with(LB_RULE_ID, + **create_rule_body) mock_successful_completion = ( self.lbv2_driver.l7rule.successful_completion) mock_successful_completion.assert_called_with(self.context, - self.l7rule) + self.l7rule, + delete=False) def test_update(self): new_l7rule = lb_models.L7Rule(L7RULE_ID, LB_TENANT_ID, @@ -732,29 +806,65 @@ class TestEdgeLbaasV2L7Rule(BaseTestEdgeLbaasV2): key='cookie1', value='xxxxx', policy=self.l7policy) - self.edge_driver.l7rule.update(self.context, self.l7rule, new_l7rule) - mock_successful_completion = ( - self.lbv2_driver.l7rule.successful_completion) - mock_successful_completion.assert_called_with( - self.context, new_l7rule) + self.l7policy.rules = [new_l7rule] + update_rule_body = { + 'match_conditions': [{ + 'type': 'LbHttpRequestHeaderCondition', + 'match_type': 'STARTS_WITH', + 'header_name': 'Cookie', + 'header_value': 'cookie1=xxxxx'}], + 'actions': [{ + 'type': 'LbSelectPoolAction', + 'pool_id': LB_POOL_ID}], + 'phase': 'HTTP_FORWARDING', + 'match_strategy': 'ALL' + } - def test_delete_pool_without_members(self): - with mock.patch.object(nsx_db, 'get_nsx_lbaas_l7rule_binding', - ) as mock_get_l7rule_binding, \ - mock.patch.object(self.vs_client, 'remove_rule' - ) as mock_remove_rule, \ - mock.patch.object(self.rule_client, 'delete', - ) as mock_delete_rule, \ - mock.patch.object(nsx_db, 'delete_nsx_lbaas_l7rule_binding', - ) as mock_delete_l7rule_binding: - mock_get_l7rule_binding.return_value = L7RULE_BINDING + with mock.patch.object(nsx_db, 'get_nsx_lbaas_l7policy_binding' + ) as mock_get_l7policy_binding, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(self.rule_client, 'update' + ) as mock_update_rule: + mock_get_l7policy_binding.return_value = L7POLICY_BINDING + mock_get_pool_binding.return_value = POOL_BINDING + + self.edge_driver.l7rule.update(self.context, self.l7rule, + new_l7rule) + + mock_update_rule.assert_called_with(LB_RULE_ID, + **update_rule_body) + + mock_successful_completion = ( + self.lbv2_driver.l7rule.successful_completion) + mock_successful_completion.assert_called_with(self.context, + new_l7rule, + delete=False) + + def test_delete(self): + self.l7policy.rules = [self.l7rule] + delete_rule_body = { + 'match_conditions': [], + 'actions': [{ + 'type': 'LbSelectPoolAction', + 'pool_id': LB_POOL_ID}], + 'phase': 'HTTP_FORWARDING', + 'match_strategy': 'ALL' + } + + with mock.patch.object(nsx_db, 'get_nsx_lbaas_l7policy_binding' + ) as mock_get_l7policy_binding, \ + mock.patch.object(nsx_db, 'get_nsx_lbaas_pool_binding' + ) as mock_get_pool_binding, \ + mock.patch.object(self.rule_client, 'update' + ) as mock_update_rule: + mock_get_l7policy_binding.return_value = L7POLICY_BINDING + mock_get_pool_binding.return_value = POOL_BINDING self.edge_driver.l7rule.delete(self.context, self.l7rule) - mock_remove_rule.assert_called_with(LB_VS_ID, LB_RULE_ID) - mock_delete_rule.assert_called_with(LB_RULE_ID) - mock_delete_l7rule_binding.assert_called_with( - self.context.session, LB_ID, L7POLICY_ID, L7RULE_ID) + mock_update_rule.assert_called_with(LB_RULE_ID, + **delete_rule_body) mock_successful_completion = ( self.lbv2_driver.l7rule.successful_completion)