diff --git a/octavia/api/drivers/amphora_driver/driver.py b/octavia/api/drivers/amphora_driver/driver.py index ff8a8d05b3..71efc06c09 100644 --- a/octavia/api/drivers/amphora_driver/driver.py +++ b/octavia/api/drivers/amphora_driver/driver.py @@ -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): diff --git a/octavia/api/drivers/utils.py b/octavia/api/drivers/utils.py index d590d151a6..1f4fb24baf 100644 --- a/octavia/api/drivers/utils.py +++ b/octavia/api/drivers/utils.py @@ -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') diff --git a/octavia/api/v2/controllers/base.py b/octavia/api/v2/controllers/base.py index 20a979fbfc..125db04d75 100644 --- a/octavia/api/v2/controllers/base.py +++ b/octavia/api/v2/controllers/base.py @@ -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, diff --git a/octavia/api/v2/controllers/l7policy.py b/octavia/api/v2/controllers/l7policy.py index 35078893a2..9aa0099706 100644 --- a/octavia/api/v2/controllers/l7policy.py +++ b/octavia/api/v2/controllers/l7policy.py @@ -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 diff --git a/octavia/api/v2/controllers/l7rule.py b/octavia/api/v2/controllers/l7rule.py index 361f3c0687..442f6c4a20 100644 --- a/octavia/api/v2/controllers/l7rule.py +++ b/octavia/api/v2/controllers/l7rule.py @@ -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) diff --git a/octavia/common/constants.py b/octavia/common/constants.py index a54d2015cc..3688541f21 100644 --- a/octavia/common/constants.py +++ b/octavia/common/constants.py @@ -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' diff --git a/octavia/tests/functional/api/v2/test_l7rule.py b/octavia/tests/functional/api/v2/test_l7rule.py index c8c1095970..eea445404e 100644 --- a/octavia/tests/functional/api/v2/test_l7rule.py +++ b/octavia/tests/functional/api/v2/test_l7rule.py @@ -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(