Implement provider drivers - L7 Rules

This patch adds provider driver support to the Octavia v2
L7 Rules API.

This patch also creates a provider driver for Octavia, fully
implementing the L7 rules methods.

Depends-On: https://review.openstack.org/570485
Change-Id: I282a98a6ccb87e9ca94a0e5d47184d23cc28b253
Story: 1655768
Task: 5165
This commit is contained in:
Michael Johnson 2018-05-08 15:42:22 -07:00 committed by Jacky Hu
parent 9ba10b0a83
commit 0fd2d35f4f
7 changed files with 137 additions and 92 deletions

View File

@ -232,7 +232,14 @@ class AmphoraProviderDriver(driver_base.ProviderDriver):
self.client.cast({}, 'delete_l7rule', **payload)
def l7rule_update(self, l7rule):
pass
l7rule_dict = l7rule.to_dict()
if 'admin_state_up' in l7rule_dict:
l7rule_dict['enabled'] = l7rule_dict.pop('admin_state_up')
l7rule_id = l7rule_dict.pop('l7rule_id')
payload = {consts.L7RULE_ID: l7rule_id,
consts.L7RULE_UPDATES: l7rule_dict}
self.client.cast({}, 'update_l7rule', **payload)
# Flavor
def get_supported_flavor_metadata(self):

View File

@ -341,11 +341,16 @@ def l7policy_dict_to_provider_dict(l7policy_dict):
def db_l7rules_to_provider_l7rules(db_l7rules):
provider_l7rules = []
for l7rule in db_l7rules:
new_l7rule_dict = l7rule_dict_to_provider_dict(l7rule.to_dict())
provider_l7rules.append(driver_dm.L7Rule.from_dict(new_l7rule_dict))
provider_l7rule = db_l7rule_to_provider_l7rule(l7rule)
provider_l7rules.append(provider_l7rule)
return provider_l7rules
def db_l7rule_to_provider_l7rule(db_l7rule):
new_l7rule_dict = l7rule_dict_to_provider_dict(db_l7rule.to_dict())
return driver_dm.L7Rule.from_dict(new_l7rule_dict)
def l7rule_dict_to_provider_dict(l7rule_dict):
new_l7rule_dict = _base_to_provider_dict(l7rule_dict)
new_l7rule_dict['l7rule_id'] = new_l7rule_dict.pop('id')

View File

@ -82,6 +82,12 @@ class BaseController(rest.RestController):
data_models.Listener, id,
show_deleted=show_deleted)
def _get_listener_and_loadbalancer_id(self, db_l7policy):
"""Get listener and loadbalancer ids from the l7policy db_model."""
load_balancer_id = db_l7policy.listener.load_balancer_id
listener_id = db_l7policy.listener_id
return load_balancer_id, listener_id
def _get_db_pool(self, session, id, show_deleted=True):
"""Get a pool from the database."""
return self._get_db_obj(session, self.repositories.pool,

View File

@ -93,12 +93,6 @@ class L7PolicyController(base.BaseController):
raise exceptions.ImmutableObject(resource='Load Balancer',
id=lb_id)
def _get_listener_and_loadbalancer_id(self, db_l7policy):
"""Get listener and loadbalancer ids from the l7policy db_model."""
load_balancer_id = db_l7policy.listener.load_balancer_id
listener_id = db_l7policy.listener_id
return load_balancer_id, listener_id
def _reset_lb_and_listener_statuses(self, session, lb_id, listener_id):
# Setting LB + listeners back to active because this should be a
# recoverable error

View File

@ -20,6 +20,9 @@ import pecan
from wsme import types as wtypes
from wsmeext import pecan as wsme_pecan
from octavia.api.drivers import data_models as driver_dm
from octavia.api.drivers import driver_factory
from octavia.api.drivers import utils as driver_utils
from octavia.api.v2.controllers import base
from octavia.api.v2.types import l7rule as l7rule_types
from octavia.common import constants
@ -39,7 +42,6 @@ class L7RuleController(base.BaseController):
def __init__(self, l7policy_id):
super(L7RuleController, self).__init__()
self.l7policy_id = l7policy_id
self.handler = self.handler.l7rule
@wsme_pecan.wsexpose(l7rule_types.L7RuleRootResponse, wtypes.text,
[wtypes.text], ignore_extra_args=True)
@ -129,23 +131,6 @@ class L7RuleController(base.BaseController):
# do not give any information as to what constraint failed
raise exceptions.InvalidOption(value='', option='')
def _send_l7rule_to_handler(self, session, db_l7rule):
try:
LOG.info("Sending Creation of L7Rule %s to handler", db_l7rule.id)
self.handler.create(db_l7rule)
except Exception:
with excutils.save_and_reraise_exception(
reraise=False), db_api.get_lock_session() as lock_session:
self._reset_lb_listener_policy_statuses(lock_session)
# L7Rule now goes to ERROR
self.repositories.l7rule.update(
lock_session, db_l7rule.id,
provisioning_status=constants.ERROR)
db_l7rule = self._get_db_l7rule(session, db_l7rule.id)
result = self._convert_db_to_type(db_l7rule,
l7rule_types.L7RuleResponse)
return l7rule_types.L7RuleRootResponse(rule=result)
@wsme_pecan.wsexpose(l7rule_types.L7RuleRootResponse,
body=l7rule_types.L7RuleRootPOST, status_code=201)
def post(self, rule_):
@ -156,13 +141,22 @@ class L7RuleController(base.BaseController):
except Exception as e:
raise exceptions.L7RuleValidation(error=e)
context = pecan.request.context.get('octavia_context')
l7rule.project_id = self._get_l7policy_project_id(context.session,
self.l7policy_id)
db_l7policy = self._get_db_l7policy(context.session, self.l7policy_id,
show_deleted=False)
load_balancer_id, listener_id = self._get_listener_and_loadbalancer_id(
db_l7policy)
l7rule.project_id, provider = self._get_lb_project_id_provider(
context.session, load_balancer_id)
self._check_l7policy_max_rules(context.session)
self._auth_validate_action(context, l7rule.project_id,
constants.RBAC_POST)
# Load the driver early as it also provides validation
driver = driver_factory.get_driver(provider)
lock_session = db_api.get_session(autocommit=False)
try:
l7rule_dict = db_prepare.create_l7rule(
@ -171,12 +165,26 @@ class L7RuleController(base.BaseController):
self._test_lb_listener_policy_statuses(context.session)
db_l7rule = self._validate_create_l7rule(lock_session, l7rule_dict)
# Prepare the data for the driver data model
provider_l7rule = (
driver_utils.db_l7rule_to_provider_l7rule(db_l7rule))
# Dispatch to the driver
LOG.info("Sending create L7 Rule %s to provider %s",
db_l7rule.id, driver.name)
driver_utils.call_provider(
driver.name, driver.l7rule_create, provider_l7rule)
lock_session.commit()
except Exception:
with excutils.save_and_reraise_exception():
lock_session.rollback()
return self._send_l7rule_to_handler(context.session, db_l7rule)
db_l7rule = self._get_db_l7rule(context.session, db_l7rule.id)
result = self._convert_db_to_type(db_l7rule,
l7rule_types.L7RuleResponse)
return l7rule_types.L7RuleRootResponse(rule=result)
def _graph_create(self, lock_session, rule_dict):
try:
@ -201,30 +209,48 @@ class L7RuleController(base.BaseController):
new_l7rule.update(l7rule.to_dict())
new_l7rule = data_models.L7Rule.from_dict(new_l7rule)
self._auth_validate_action(context, db_l7rule.project_id,
constants.RBAC_PUT)
db_l7policy = self._get_db_l7policy(context.session, self.l7policy_id,
show_deleted=False)
load_balancer_id, listener_id = self._get_listener_and_loadbalancer_id(
db_l7policy)
project_id, provider = self._get_lb_project_id_provider(
context.session, load_balancer_id)
self._auth_validate_action(context, project_id, constants.RBAC_PUT)
try:
validate.l7rule_data(new_l7rule)
except Exception as e:
raise exceptions.L7RuleValidation(error=e)
self._test_lb_listener_policy_statuses(context.session)
self.repositories.l7rule.update(
context.session, db_l7rule.id,
provisioning_status=constants.PENDING_UPDATE)
# Load the driver early as it also provides validation
driver = driver_factory.get_driver(provider)
try:
LOG.info("Sending Update of L7Rule %s to handler", id)
self.handler.update(db_l7rule, l7rule)
except Exception:
with excutils.save_and_reraise_exception(
reraise=False), db_api.get_lock_session() as lock_session:
self._reset_lb_listener_policy_statuses(lock_session)
# L7Rule now goes to ERROR
self.repositories.l7rule.update(
lock_session, db_l7rule.id,
provisioning_status=constants.ERROR)
with db_api.get_lock_session() as lock_session:
self._test_lb_listener_policy_statuses(lock_session)
# Prepare the data for the driver data model
l7rule_dict = l7rule.to_dict(render_unsets=False)
l7rule_dict['id'] = id
provider_l7rule_dict = (
driver_utils.l7rule_dict_to_provider_dict(l7rule_dict))
# Dispatch to the driver
LOG.info("Sending update L7 Rule %s to provider %s", id,
driver.name)
driver_utils.call_provider(
driver.name, driver.l7rule_update,
driver_dm.L7Rule.from_dict(provider_l7rule_dict))
# Update the database to reflect what the driver just accepted
l7rule.provisioning_status = constants.PENDING_UPDATE
db_l7rule_dict = l7rule.to_dict(render_unsets=False)
self.repositories.l7rule.update(lock_session, id, **db_l7rule_dict)
# Force SQL alchemy to query the DB, otherwise we get inconsistent
# results
context.session.expire_all()
db_l7rule = self._get_db_l7rule(context.session, id)
result = self._convert_db_to_type(db_l7rule,
l7rule_types.L7RuleResponse)
@ -237,26 +263,29 @@ class L7RuleController(base.BaseController):
db_l7rule = self._get_db_l7rule(context.session, id,
show_deleted=False)
self._auth_validate_action(context, db_l7rule.project_id,
constants.RBAC_DELETE)
db_l7policy = self._get_db_l7policy(context.session, self.l7policy_id,
show_deleted=False)
load_balancer_id, listener_id = self._get_listener_and_loadbalancer_id(
db_l7policy)
project_id, provider = self._get_lb_project_id_provider(
context.session, load_balancer_id)
self._auth_validate_action(context, project_id, constants.RBAC_DELETE)
if db_l7rule.provisioning_status == constants.DELETED:
return
self._test_lb_listener_policy_statuses(context.session)
# Load the driver early as it also provides validation
driver = driver_factory.get_driver(provider)
self.repositories.l7rule.update(
context.session, db_l7rule.id,
provisioning_status=constants.PENDING_DELETE)
with db_api.get_lock_session() as lock_session:
try:
LOG.info("Sending Deletion of L7Rule %s to handler", db_l7rule.id)
self.handler.delete(db_l7rule)
except Exception:
with excutils.save_and_reraise_exception(
reraise=False), db_api.get_lock_session() as lock_session:
self._reset_lb_listener_policy_statuses(lock_session)
# L7Rule now goes to ERROR
self.repositories.l7rule.update(
lock_session, db_l7rule.id,
provisioning_status=constants.ERROR)
self._test_lb_listener_policy_statuses(lock_session)
self.repositories.l7rule.update(
lock_session, db_l7rule.id,
provisioning_status=constants.PENDING_DELETE)
LOG.info("Sending delete L7 Rule %s to provider %s", id,
driver.name)
driver_utils.call_provider(driver.name, driver.l7rule_delete, id)

View File

@ -231,6 +231,7 @@ POOL_UPDATES = 'pool_updates'
MEMBER_UPDATES = 'member_updates'
HEALTH_MONITOR_UPDATES = 'health_monitor_updates'
L7POLICY_UPDATES = 'l7policy_updates'
L7RULE_UPDATES = 'l7rule_updates'
CERT_ROTATE_AMPHORA_FLOW = 'octavia-cert-rotate-amphora-flow'
CREATE_AMPHORA_FLOW = 'octavia-create-amphora-flow'

View File

@ -19,6 +19,7 @@ from oslo_utils import uuidutils
from octavia.common import constants
import octavia.common.context
from octavia.common import exceptions
from octavia.tests.functional.api.v2 import base
@ -575,17 +576,19 @@ class TestL7Rule(base.BaseAPITest):
'value': 'some-string'}
self.post(self.l7rules_path, self._build_body(l7rule), status=400)
def test_create_with_bad_handler(self):
self.handler_mock().l7rule.create.side_effect = Exception()
api_l7rule = self.create_l7rule(
self.l7policy_id, constants.L7RULE_TYPE_PATH,
constants.L7RULE_COMPARE_TYPE_STARTS_WITH,
'/api').get(self.root_tag)
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('id'),
l7rule_prov_status=constants.ERROR,
l7rule_op_status=constants.OFFLINE)
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_create_with_bad_provider(self, mock_provider):
mock_provider.side_effect = exceptions.ProviderDriverError(
prov='bad_driver', user_msg='broken')
l7rule = {'compare_type': 'REGEX',
'invert': False,
'type': 'PATH',
'value': '/images*',
'admin_state_up': True}
response = self.post(self.l7rules_path, self._build_body(l7rule),
status=500)
self.assertIn('Provider \'bad_driver\' reports error: broken',
response.json.get('faultstring'))
def test_update(self):
api_l7rule = self.create_l7rule(
@ -597,7 +600,7 @@ class TestL7Rule(base.BaseAPITest):
response = self.put(self.l7rule_path.format(
l7rule_id=api_l7rule.get('id')),
self._build_body(new_l7rule)).json.get(self.root_tag)
self.assertEqual('/api', response.get('value'))
self.assertEqual('/images', response.get('value'))
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('id'),
@ -639,7 +642,7 @@ class TestL7Rule(base.BaseAPITest):
l7rule_id=api_l7rule.get('id')),
self._build_body(new_l7rule)).json.get(self.root_tag)
self.conf.config(group='api_settings', auth_strategy=auth_strategy)
self.assertEqual('/api', response.get('value'))
self.assertEqual('/images', response.get('value'))
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('id'),
@ -683,20 +686,21 @@ class TestL7Rule(base.BaseAPITest):
self.put(self.l7rule_path.format(l7rule_id=l7rule.get('id')),
self._build_body(new_l7rule), status=400)
def test_update_with_bad_handler(self):
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_update_with_bad_provider(self, mock_provider):
api_l7rule = self.create_l7rule(
self.l7policy_id, constants.L7RULE_TYPE_PATH,
constants.L7RULE_COMPARE_TYPE_STARTS_WITH,
'/api').get(self.root_tag)
self.set_lb_status(self.lb_id)
new_l7rule = {'value': '/images'}
self.handler_mock().l7rule.update.side_effect = Exception()
self.put(self.l7rule_path.format(
l7rule_id=api_l7rule.get('id')), self._build_body(new_l7rule))
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('id'),
l7rule_prov_status=constants.ERROR)
mock_provider.side_effect = exceptions.ProviderDriverError(
prov='bad_driver', user_msg='broken')
response = self.put(
self.l7rule_path.format(l7rule_id=api_l7rule.get('id')),
self._build_body(new_l7rule), status=500)
self.assertIn('Provider \'bad_driver\' reports error: broken',
response.json.get('faultstring'))
def test_update_with_invalid_rule(self):
api_l7rule = self.create_l7rule(
@ -827,7 +831,8 @@ class TestL7Rule(base.BaseAPITest):
self.delete(self.l7rule_path.format(
l7rule_id=uuidutils.generate_uuid()), status=404)
def test_delete_with_bad_handler(self):
@mock.patch('octavia.api.drivers.utils.call_provider')
def test_delete_with_bad_provider(self, mock_provider):
api_l7rule = self.create_l7rule(
self.l7policy_id, constants.L7RULE_TYPE_PATH,
constants.L7RULE_COMPARE_TYPE_STARTS_WITH,
@ -843,12 +848,10 @@ class TestL7Rule(base.BaseAPITest):
self.assertIsNotNone(response.pop('updated_at'))
self.assertEqual(api_l7rule, response)
self.handler_mock().l7rule.delete.side_effect = Exception()
self.delete(self.l7rule_path.format(l7rule_id=api_l7rule.get('id')))
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('id'),
l7rule_prov_status=constants.ERROR)
mock_provider.side_effect = exceptions.ProviderDriverError(
prov='bad_driver', user_msg='broken')
self.delete(self.l7rule_path.format(l7rule_id=api_l7rule.get('id')),
status=500)
def test_create_when_lb_pending_update(self):
self.create_l7rule(