Merge "Implement provider drivers - L7 Rules"

This commit is contained in:
Zuul 2018-06-05 01:57:35 +00:00 committed by Gerrit Code Review
commit 5e22898061
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) self.client.cast({}, 'delete_l7rule', **payload)
def l7rule_update(self, l7rule): 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 # Flavor
def get_supported_flavor_metadata(self): 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): def db_l7rules_to_provider_l7rules(db_l7rules):
provider_l7rules = [] provider_l7rules = []
for l7rule in db_l7rules: for l7rule in db_l7rules:
new_l7rule_dict = l7rule_dict_to_provider_dict(l7rule.to_dict()) provider_l7rule = db_l7rule_to_provider_l7rule(l7rule)
provider_l7rules.append(driver_dm.L7Rule.from_dict(new_l7rule_dict)) provider_l7rules.append(provider_l7rule)
return provider_l7rules 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): def l7rule_dict_to_provider_dict(l7rule_dict):
new_l7rule_dict = _base_to_provider_dict(l7rule_dict) new_l7rule_dict = _base_to_provider_dict(l7rule_dict)
new_l7rule_dict['l7rule_id'] = new_l7rule_dict.pop('id') new_l7rule_dict['l7rule_id'] = new_l7rule_dict.pop('id')

View File

@ -82,6 +82,12 @@ class BaseController(rest.RestController):
data_models.Listener, id, data_models.Listener, id,
show_deleted=show_deleted) 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): def _get_db_pool(self, session, id, show_deleted=True):
"""Get a pool from the database.""" """Get a pool from the database."""
return self._get_db_obj(session, self.repositories.pool, 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', raise exceptions.ImmutableObject(resource='Load Balancer',
id=lb_id) 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): def _reset_lb_and_listener_statuses(self, session, lb_id, listener_id):
# Setting LB + listeners back to active because this should be a # Setting LB + listeners back to active because this should be a
# recoverable error # recoverable error

View File

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

View File

@ -231,6 +231,7 @@ POOL_UPDATES = 'pool_updates'
MEMBER_UPDATES = 'member_updates' MEMBER_UPDATES = 'member_updates'
HEALTH_MONITOR_UPDATES = 'health_monitor_updates' HEALTH_MONITOR_UPDATES = 'health_monitor_updates'
L7POLICY_UPDATES = 'l7policy_updates' L7POLICY_UPDATES = 'l7policy_updates'
L7RULE_UPDATES = 'l7rule_updates'
CERT_ROTATE_AMPHORA_FLOW = 'octavia-cert-rotate-amphora-flow' CERT_ROTATE_AMPHORA_FLOW = 'octavia-cert-rotate-amphora-flow'
CREATE_AMPHORA_FLOW = 'octavia-create-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 from octavia.common import constants
import octavia.common.context import octavia.common.context
from octavia.common import exceptions
from octavia.tests.functional.api.v2 import base from octavia.tests.functional.api.v2 import base
@ -575,17 +576,19 @@ class TestL7Rule(base.BaseAPITest):
'value': 'some-string'} 'value': 'some-string'}
self.post(self.l7rules_path, self._build_body(l7rule), status=400) self.post(self.l7rules_path, self._build_body(l7rule), status=400)
def test_create_with_bad_handler(self): @mock.patch('octavia.api.drivers.utils.call_provider')
self.handler_mock().l7rule.create.side_effect = Exception() def test_create_with_bad_provider(self, mock_provider):
api_l7rule = self.create_l7rule( mock_provider.side_effect = exceptions.ProviderDriverError(
self.l7policy_id, constants.L7RULE_TYPE_PATH, prov='bad_driver', user_msg='broken')
constants.L7RULE_COMPARE_TYPE_STARTS_WITH, l7rule = {'compare_type': 'REGEX',
'/api').get(self.root_tag) 'invert': False,
self.assert_correct_status( 'type': 'PATH',
lb_id=self.lb_id, listener_id=self.listener_id, 'value': '/images*',
l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('id'), 'admin_state_up': True}
l7rule_prov_status=constants.ERROR, response = self.post(self.l7rules_path, self._build_body(l7rule),
l7rule_op_status=constants.OFFLINE) status=500)
self.assertIn('Provider \'bad_driver\' reports error: broken',
response.json.get('faultstring'))
def test_update(self): def test_update(self):
api_l7rule = self.create_l7rule( api_l7rule = self.create_l7rule(
@ -597,7 +600,7 @@ class TestL7Rule(base.BaseAPITest):
response = self.put(self.l7rule_path.format( response = self.put(self.l7rule_path.format(
l7rule_id=api_l7rule.get('id')), l7rule_id=api_l7rule.get('id')),
self._build_body(new_l7rule)).json.get(self.root_tag) 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( self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id, lb_id=self.lb_id, listener_id=self.listener_id,
l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('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')), l7rule_id=api_l7rule.get('id')),
self._build_body(new_l7rule)).json.get(self.root_tag) self._build_body(new_l7rule)).json.get(self.root_tag)
self.conf.config(group='api_settings', auth_strategy=auth_strategy) 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( self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id, lb_id=self.lb_id, listener_id=self.listener_id,
l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('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.put(self.l7rule_path.format(l7rule_id=l7rule.get('id')),
self._build_body(new_l7rule), status=400) 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( api_l7rule = self.create_l7rule(
self.l7policy_id, constants.L7RULE_TYPE_PATH, self.l7policy_id, constants.L7RULE_TYPE_PATH,
constants.L7RULE_COMPARE_TYPE_STARTS_WITH, constants.L7RULE_COMPARE_TYPE_STARTS_WITH,
'/api').get(self.root_tag) '/api').get(self.root_tag)
self.set_lb_status(self.lb_id) self.set_lb_status(self.lb_id)
new_l7rule = {'value': '/images'} new_l7rule = {'value': '/images'}
self.handler_mock().l7rule.update.side_effect = Exception() mock_provider.side_effect = exceptions.ProviderDriverError(
self.put(self.l7rule_path.format( prov='bad_driver', user_msg='broken')
l7rule_id=api_l7rule.get('id')), self._build_body(new_l7rule)) response = self.put(
self.assert_correct_status( self.l7rule_path.format(l7rule_id=api_l7rule.get('id')),
lb_id=self.lb_id, listener_id=self.listener_id, self._build_body(new_l7rule), status=500)
l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('id'), self.assertIn('Provider \'bad_driver\' reports error: broken',
l7rule_prov_status=constants.ERROR) response.json.get('faultstring'))
def test_update_with_invalid_rule(self): def test_update_with_invalid_rule(self):
api_l7rule = self.create_l7rule( api_l7rule = self.create_l7rule(
@ -827,7 +831,8 @@ class TestL7Rule(base.BaseAPITest):
self.delete(self.l7rule_path.format( self.delete(self.l7rule_path.format(
l7rule_id=uuidutils.generate_uuid()), status=404) 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( api_l7rule = self.create_l7rule(
self.l7policy_id, constants.L7RULE_TYPE_PATH, self.l7policy_id, constants.L7RULE_TYPE_PATH,
constants.L7RULE_COMPARE_TYPE_STARTS_WITH, constants.L7RULE_COMPARE_TYPE_STARTS_WITH,
@ -843,12 +848,10 @@ class TestL7Rule(base.BaseAPITest):
self.assertIsNotNone(response.pop('updated_at')) self.assertIsNotNone(response.pop('updated_at'))
self.assertEqual(api_l7rule, response) self.assertEqual(api_l7rule, response)
self.handler_mock().l7rule.delete.side_effect = Exception() mock_provider.side_effect = exceptions.ProviderDriverError(
self.delete(self.l7rule_path.format(l7rule_id=api_l7rule.get('id'))) prov='bad_driver', user_msg='broken')
self.assert_correct_status( self.delete(self.l7rule_path.format(l7rule_id=api_l7rule.get('id')),
lb_id=self.lb_id, listener_id=self.listener_id, status=500)
l7policy_id=self.l7policy_id, l7rule_id=api_l7rule.get('id'),
l7rule_prov_status=constants.ERROR)
def test_create_when_lb_pending_update(self): def test_create_when_lb_pending_update(self):
self.create_l7rule( self.create_l7rule(