From aa9da74ca726274ee603e0ea6f8e75ac5711284d Mon Sep 17 00:00:00 2001
From: Tong Liu <tongl@vmware.com>
Date: Fri, 21 Jul 2017 16:07:25 -0700
Subject: [PATCH] NSXv3: Add Neutron LBaaS Layer7 Support

This patch adds support for LBaaS layer7 and implement
the following LBaaS resources:
  - L7 Policy
  - L7 Rule

Change-Id: If5a0fbeecae7c7d6e98667a1feb3a15aff3f7d70
---
 vmware_nsx/db/db.py                           |  28 +++
 .../ea7a72ab9643_nsxv3_lbaas_mapping.py       |  17 ++
 vmware_nsx/db/nsx_models.py                   |  14 ++
 vmware_nsx/services/lbaas/lb_const.py         |   6 +-
 .../services/lbaas/nsx_v3/l7policy_mgr.py     |  53 +++++
 .../services/lbaas/nsx_v3/l7rule_mgr.py       | 189 ++++++++++++++++++
 .../services/lbaas/nsx_v3/lb_driver_v2.py     |   4 +
 vmware_nsx/services/lbaas/nsx_v3/pool_mgr.py  |   4 +-
 .../unit/services/lbaas/test_nsxv3_driver.py  | 145 +++++++++++++-
 9 files changed, 457 insertions(+), 3 deletions(-)
 create mode 100644 vmware_nsx/services/lbaas/nsx_v3/l7policy_mgr.py
 create mode 100644 vmware_nsx/services/lbaas/nsx_v3/l7rule_mgr.py

diff --git a/vmware_nsx/db/db.py b/vmware_nsx/db/db.py
index c56d37fbb6..c89502dce6 100644
--- a/vmware_nsx/db/db.py
+++ b/vmware_nsx/db/db.py
@@ -593,3 +593,31 @@ def delete_nsx_lbaas_monitor_binding(session, loadbalancer_id, pool_id,
     return (session.query(nsx_models.NsxLbaasMonitor).
             filter_by(loadbalancer_id=loadbalancer_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):
+    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)
+        session.add(binding)
+    return binding
+
+
+def get_nsx_lbaas_l7rule_binding(session, loadbalancer_id, l7policy_id,
+                                 l7rule_id):
+    try:
+        return session.query(nsx_models.NsxLbaasL7Rule).filter_by(
+            loadbalancer_id=loadbalancer_id, l7policy_id=l7policy_id,
+            l7rule_id=l7rule_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())
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
index f198501f84..7bdb7d5213 100644
--- 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
@@ -72,6 +72,18 @@ def upgrade():
         sa.Column('updated_at', sa.DateTime(), nullable=True),
         sa.PrimaryKeyConstraint('loadbalancer_id', 'pool_id', 'hm_id'))
 
+    op.create_table(
+        'nsxv3_lbaas_l7rules',
+        sa.Column('loadbalancer_id', sa.String(36), nullable=False),
+        sa.Column('l7policy_id', sa.String(36), nullable=False),
+        sa.Column('l7rule_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('loadbalancer_id', 'l7policy_id',
+                                'l7rule_id'))
+
     if migration.schema_has_table('lbaas_loadbalancers'):
         op.create_foreign_key(
             'fk_nsxv3_lbaas_loadbalancers_id', 'nsxv3_lbaas_loadbalancers',
@@ -92,3 +104,8 @@ def upgrade():
         op.create_foreign_key(
             'fk_nsxv3_lbaas_healthmonitors_id', 'nsxv3_lbaas_monitors',
             'lbaas_healthmonitors', ['hm_id'], ['id'], ondelete='CASCADE')
+
+    if migration.schema_has_table('lbaas_l7rules'):
+        op.create_foreign_key(
+            'fk_nsxv3_lbaas_l7rules_id', 'nsxv3_lbaas_l7rules',
+            'lbaas_l7rules', ['l7rule_id'], ['id'], ondelete='CASCADE')
diff --git a/vmware_nsx/db/nsx_models.py b/vmware_nsx/db/nsx_models.py
index a75d0b8ac5..c9c4a3087c 100644
--- a/vmware_nsx/db/nsx_models.py
+++ b/vmware_nsx/db/nsx_models.py
@@ -448,3 +448,17 @@ class NsxLbaasMonitor(model_base.BASEV2, models.TimestampMixin):
                       primary_key=True)
     lb_monitor_id = sa.Column(sa.String(36), nullable=False)
     lb_pool_id = sa.Column(sa.String(36), nullable=False)
+
+
+class NsxLbaasL7Rule(model_base.BASEV2, models.TimestampMixin):
+    """Stores the mapping between LBaaS monitor and NSX LB monitor"""
+    __tablename__ = 'nsxv3_lbaas_l7rules'
+    loadbalancer_id = sa.Column(sa.String(36), primary_key=True)
+    l7policy_id = sa.Column(sa.String(36), primary_key=True)
+    l7rule_id = sa.Column(sa.String(36),
+                          sa.ForeignKey('lbaas_l7rules.id',
+                                        name='fk_nsxv3_lbaas_l7rules_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 80899f5b27..d4ac60a9b5 100644
--- a/vmware_nsx/services/lbaas/lb_const.py
+++ b/vmware_nsx/services/lbaas/lb_const.py
@@ -77,7 +77,8 @@ L7_RULE_COMPARE_TYPE_EQUAL_TO = 'EQUAL_TO'
 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_POOL_TYPE = 'os-lbaas-pool-id'
+LB_L7RULE_TYPE = 'os-lbaas-l7rule-id'
 LB_HTTP_PROFILE = 'LbHttpProfile'
 LB_TCP_PROFILE = 'LbFastTcpProfile'
 NSXV3_MONITOR_MAP = {LB_HEALTH_MONITOR_PING: 'LbIcmpMonitor',
@@ -88,6 +89,9 @@ LB_STATS_MAP = {'active_connections': 'current_sessions',
                 'bytes_in': 'bytes_in',
                 'bytes_out': 'bytes_out',
                 'total_connections': 'total_sessions'}
+LB_SELECT_POOL_ACTION = 'LbSelectPoolAction'
+LB_HTTP_REDIRECT_ACTION = 'LbHttpRedirectAction'
+LB_REJECT_ACTION = 'LbHttpRejectAction'
 LR_ROUTER_TYPE = 'os-neutron-router-id'
 LR_PORT_TYPE = 'os-neutron-rport-id'
 DEFAULT_LB_SIZE = 'SMALL'
diff --git a/vmware_nsx/services/lbaas/nsx_v3/l7policy_mgr.py b/vmware_nsx/services/lbaas/nsx_v3/l7policy_mgr.py
new file mode 100644
index 0000000000..92ec121dce
--- /dev/null
+++ b/vmware_nsx/services/lbaas/nsx_v3/l7policy_mgr.py
@@ -0,0 +1,53 @@
+# 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.services.lbaas import base_mgr
+
+LOG = logging.getLogger(__name__)
+
+
+class EdgeL7PolicyManager(base_mgr.Nsxv3LoadbalancerBaseManager):
+    @log_helpers.log_method_call
+    def __init__(self):
+        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)
+
+    @log_helpers.log_method_call
+    def create(self, context, policy):
+        self._l7policy_action(context, policy, 'create')
+
+    @log_helpers.log_method_call
+    def update(self, context, old_policy, new_policy):
+        self._l7policy_action(context, new_policy, 'update')
+
+    @log_helpers.log_method_call
+    def delete(self, context, policy):
+        self._l7policy_action(context, policy, 'delete', delete=True)
diff --git a/vmware_nsx/services/lbaas/nsx_v3/l7rule_mgr.py b/vmware_nsx/services/lbaas/nsx_v3/l7rule_mgr.py
new file mode 100644
index 0000000000..15bf273bfd
--- /dev/null
+++ b/vmware_nsx/services/lbaas/nsx_v3/l7rule_mgr.py
@@ -0,0 +1,189 @@
+# 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
+
+LOG = logging.getLogger(__name__)
+
+
+class EdgeL7RuleManager(base_mgr.Nsxv3LoadbalancerBaseManager):
+    @log_helpers.log_method_call
+    def __init__(self):
+        super(EdgeL7RuleManager, self).__init__()
+
+    @log_helpers.log_method_call
+    def _get_rule_match_conditions(self, rule):
+        match_conditions = []
+        if rule.type == lb_const.L7_RULE_TYPE_COOKIE:
+            header_value = rule.key + '=' + rule.value
+            match_conditions.append(
+                {'type': 'LbHttpRequestHeaderCondition',
+                 'header_name': 'Cookie',
+                 'header_value': header_value})
+        elif rule.type == lb_const.L7_RULE_TYPE_FILE_TYPE:
+            match_conditions.append(
+                {'type': 'LbHttpRequestUriCondition',
+                 'uri': '*.' + rule.value})
+        elif rule.type == lb_const.L7_RULE_TYPE_HEADER:
+            match_conditions.append(
+                {'type': 'LbHttpRequestHeaderCondition',
+                 'header_name': rule.key,
+                 'header_value': rule.value})
+        elif rule.type == lb_const.L7_RULE_TYPE_HOST_NAME:
+            match_conditions.append(
+                {'type': 'LbHttpRequestHeaderCondition',
+                 'header_name': 'Host',
+                 'header_value': rule.value})
+        elif rule.type == lb_const.L7_RULE_TYPE_PATH:
+            match_conditions.append(
+                {'type': 'LbHttpRequestUriCondition',
+                 '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)
+
+    @log_helpers.log_method_call
+    def _convert_l7policy_to_rule(self, context, rule):
+        lb_id = rule.policy.listener.loadbalancer_id
+        body = {}
+        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']
+                body['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:
+            body['actions'] = [{'type': lb_const.LB_HTTP_REDIRECT_ACTION,
+                               'redirect_rul': l7policy.redirect_url}]
+        elif l7policy.action == lb_const.L7_POLICY_ACTION_REJECT:
+            body['actions'] = [{'type': lb_const.LB_REJECT_ACTION}]
+        else:
+            msg = (_('Invalid l7policy action: %(action)s') %
+                   {'action': l7policy.action})
+            raise n_exc.BadRequest(resource='lbaas-l7rule-create',
+                                   msg=msg)
+        return body
+
+    @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_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)% 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)
+
+    @log_helpers.log_method_call
+    def update(self, context, old_rule, new_rule):
+        self.lbv2_driver.l7rule.successful_completion(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:
+                rule_client.delete(rule_id)
+            except nsx_exc.NsxResourceNotFound:
+                msg = (_("LB rule cannot be found on nsx: %(rule)s") %
+                       {'rule': rule_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 delete lb rule: %(rule)s') %
+                       {'rule': rule.id})
+                raise n_exc.BadRequest(resource='lbaas-l7rule-delete',
+                                       msg=msg)
+            try:
+                lb_vs = vs_client.get(vs_id)
+                if 'rule_ids' in lb_vs and rule_id in lb_vs['rule_ids']:
+                    lb_vs['rule_ids'].remove(rule_id)
+                vs_client.update(vs_id, lb_vs)
+            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)
+
+            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)
diff --git a/vmware_nsx/services/lbaas/nsx_v3/lb_driver_v2.py b/vmware_nsx/services/lbaas/nsx_v3/lb_driver_v2.py
index 223e509b2e..1dac1f9064 100644
--- a/vmware_nsx/services/lbaas/nsx_v3/lb_driver_v2.py
+++ b/vmware_nsx/services/lbaas/nsx_v3/lb_driver_v2.py
@@ -17,6 +17,8 @@ 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 l7policy_mgr
+from vmware_nsx.services.lbaas.nsx_v3 import l7rule_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
@@ -50,6 +52,8 @@ class EdgeLoadbalancerDriverV2(object):
         self.pool = pool_mgr.EdgePoolManager()
         self.member = member_mgr.EdgeMemberManager()
         self.healthmonitor = hm_mgr.EdgeHealthMonitorManager()
+        self.l7policy = l7policy_mgr.EdgeL7PolicyManager()
+        self.l7rule = l7rule_mgr.EdgeL7RuleManager()
 
 
 class DummyLoadbalancerDriverV2(object):
diff --git a/vmware_nsx/services/lbaas/nsx_v3/pool_mgr.py b/vmware_nsx/services/lbaas/nsx_v3/pool_mgr.py
index 762e029ec0..cf11d50ca6 100644
--- a/vmware_nsx/services/lbaas/nsx_v3/pool_mgr.py
+++ b/vmware_nsx/services/lbaas/nsx_v3/pool_mgr.py
@@ -46,9 +46,11 @@ class EdgePoolManager(base_mgr.Nsxv3LoadbalancerBaseManager):
                                  lb_const.LB_POOL_TYPE, pool.tenant_id,
                                  context.project_name)
         try:
+            snat_translation = {'type': "LbSnatAutoMap"}
             lb_pool = pool_client.create(display_name=pool_name,
                                          tags=tags,
-                                         algorithm=pool.lb_algorithm)
+                                         algorithm=pool.lb_algorithm,
+                                         snat_translation=snat_translation)
         except nsxlib_exc.ManagerError:
             self.lbv2_driver.pool.failed_completion(context, pool)
             msg = (_('Failed to create pool on NSX backend: %(pool)s') %
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 0a6c537572..96c19f19e0 100644
--- a/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py
+++ b/vmware_nsx/tests/unit/services/lbaas/test_nsxv3_driver.py
@@ -94,6 +94,14 @@ HM_BINDING = {'loadbalancer_id': LB_ID,
               'hm_id': HM_ID,
               'lb_monitor_id': LB_MONITOR_ID,
               'lb_pool_id': LB_POOL_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}
 
 
 class BaseTestEdgeLbaasV2(base.BaseTestCase):
@@ -130,6 +138,22 @@ class BaseTestEdgeLbaasV2(base.BaseTestCase):
                                        name='member-mmm-mmm')
         self.hm = lb_models.HealthMonitor(HM_ID, LB_TENANT_ID, 'PING', 3, 3,
                                           1, pool=self.pool, name='hm1')
+        self.l7policy = lb_models.L7Policy(L7POLICY_ID, LB_TENANT_ID,
+                                           name='policy-test',
+                                           description='policy-desc',
+                                           listener_id=LISTENER_ID,
+                                           action='REDIRECT_TO_POOL',
+                                           redirect_pool_id=LB_POOL_ID,
+                                           listener=self.listener,
+                                           position=1)
+        self.l7rule = lb_models.L7Rule(L7RULE_ID, LB_TENANT_ID,
+                                       l7policy_id=L7POLICY_ID,
+                                       compare_type='EQUAL_TO',
+                                       invert=False,
+                                       type='HEADER',
+                                       key='key1',
+                                       value='val1',
+                                       policy=self.l7policy)
 
     def tearDown(self):
         self._unpatch_lb_plugin(self.lbv2_driver, self._tested_entity)
@@ -156,6 +180,8 @@ class BaseTestEdgeLbaasV2(base.BaseTestCase):
                                              'pool').start()
         self.monitor_client = mock.patch.object(load_balancer,
                                                 'monitor').start()
+        self.rule_client = mock.patch.object(load_balancer,
+                                             'rule').start()
 
     def _unpatch_lb_plugin(self, lb_plugin, manager):
         setattr(lb_plugin, manager, self.real_manager)
@@ -487,7 +513,7 @@ class TestEdgeLbaasV2HealthMonitor(BaseTestEdgeLbaasV2):
     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)
+        self.edge_driver.healthmonitor.update(self.context, self.hm, new_hm)
 
         mock_successful_completion = (
             self.lbv2_driver.health_monitor.successful_completion)
@@ -517,3 +543,120 @@ class TestEdgeLbaasV2HealthMonitor(BaseTestEdgeLbaasV2):
             mock_successful_completion.assert_called_with(self.context,
                                                           self.hm,
                                                           delete=True)
+
+
+class TestEdgeLbaasV2L7Policy(BaseTestEdgeLbaasV2):
+    def setUp(self):
+        super(TestEdgeLbaasV2L7Policy, self).setUp()
+
+    @property
+    def _tested_entity(self):
+        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)
+
+    def test_update(self):
+        new_l7policy = lb_models.L7Policy(L7POLICY_ID, LB_TENANT_ID,
+                                          name='new-policy',
+                                          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)
+
+    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)
+
+
+class TestEdgeLbaasV2L7Rule(BaseTestEdgeLbaasV2):
+    def setUp(self):
+        super(TestEdgeLbaasV2L7Rule, self).setUp()
+
+    @property
+    def _tested_entity(self):
+        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',
+                              ) 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_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_successful_completion = (
+                self.lbv2_driver.l7rule.successful_completion)
+            mock_successful_completion.assert_called_with(self.context,
+                                                          self.l7rule)
+
+    def test_update(self):
+        new_l7rule = lb_models.L7Rule(L7RULE_ID, LB_TENANT_ID,
+                                      l7policy_id=L7POLICY_ID,
+                                      compare_type='STARTS_WITH',
+                                      invert=True,
+                                      type='COOKIE',
+                                      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)
+
+    def test_delete(self):
+        with mock.patch.object(nsx_db, 'get_nsx_lbaas_l7rule_binding',
+                               ) as mock_get_l7rule_binding, \
+            mock.patch.object(self.rule_client, 'delete',
+                              ) as mock_delete_rule, \
+            mock.patch.object(self.vs_client, 'get',
+                              ) as mock_get_vs, \
+            mock.patch.object(self.vs_client, 'update',
+                              ) as mock_update_vs, \
+            mock.patch.object(nsx_db, 'delete_nsx_lbaas_l7rule_binding',
+                              ) as mock_delete_l7rule_binding:
+            mock_get_l7rule_binding.return_value = L7RULE_BINDING
+            mock_get_vs.return_value = {'id': LB_VS_ID,
+                                        'rule_ids': [LB_RULE_ID]}
+
+            self.edge_driver.l7rule.delete(self.context, self.l7rule)
+
+            mock_delete_rule.assert_called_with(LB_RULE_ID)
+            mock_update_vs.assert_called_with(LB_VS_ID, {'id': LB_VS_ID,
+                                                         'rule_ids': []})
+            mock_delete_l7rule_binding.assert_called_with(
+                self.context.session, LB_ID, L7POLICY_ID, L7RULE_ID)
+
+            mock_successful_completion = (
+                self.lbv2_driver.l7rule.successful_completion)
+            mock_successful_completion.assert_called_with(self.context,
+                                                          self.l7rule,
+                                                          delete=True)