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:
parent
9ba10b0a83
commit
0fd2d35f4f
@ -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):
|
||||
|
@ -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')
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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'
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user