Remove singleton pattern from API controllers

This fixes some testing issues, and I don't think it was intended to
begin with...

Also fixes a bug in the v2 listeners controller that was exposed by this
testing, so the LB will return to ACTIVE status as expected on failure
to create a listener.

Depends-On: I77658cca1aa82240409a91a2d040c105107ff2e2
Change-Id: Ib23a5253acf30b1a820d2d3fba2e60d2f67d321e
This commit is contained in:
Adam Harwell 2017-04-19 07:11:04 +09:00
parent 04a347a8d1
commit 13730f5bfe
9 changed files with 149 additions and 45 deletions

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import pecan
from pecan import rest
from wsme import types as wtypes
from wsmeext import pecan as wsme_pecan
@ -22,8 +21,13 @@ from octavia.api.v2 import controllers as v2_controller
class RootController(rest.RestController):
"""The controller in which the pecan wsgi app should be created with."""
v1 = v1_controller.V1Controller()
"""The controller with which the pecan wsgi app should be created."""
v1 = None
def __init__(self):
super(RootController, self).__init__()
self.v1 = v1_controller.V1Controller()
setattr(self, 'v2.0', v2_controller.V2Controller())
@wsme_pecan.wsexpose(wtypes.text)
def get(self):
@ -36,8 +40,3 @@ class RootController(rest.RestController):
'updated': '2016-12-11T00:00:00Z',
'id': 'v2.0'}
]}
# This controller cannot be specified directly as a member of RootController
# as its path is not a valid python identifier
pecan.route(RootController, 'v2.0', v2_controller.V2Controller())

View File

@ -22,8 +22,13 @@ from octavia.api.v1.controllers import quotas
class V1Controller(base.BaseController):
loadbalancers = load_balancer.LoadBalancersController()
quotas = quotas.QuotasController()
loadbalancers = None
quotas = None
def __init__(self):
super(V1Controller, self).__init__()
self.loadbalancers = load_balancer.LoadBalancersController()
self.quotas = quotas.QuotasController()
@wsme_pecan.wsexpose(wtypes.text)
def get(self):

View File

@ -23,10 +23,17 @@ from octavia.api.v2.controllers import pool
class BaseV2Controller(base.BaseController):
loadbalancers = load_balancer.LoadBalancersController()
listeners = listener.ListenersController()
pools = pool.PoolsController()
l7policies = l7policy.L7PolicyController()
loadbalancers = None
listeners = None
pools = None
l7policies = None
def __init__(self):
super(BaseV2Controller, self).__init__()
self.loadbalancers = load_balancer.LoadBalancersController()
self.listeners = listener.ListenersController()
self.pools = pool.PoolsController()
self.l7policies = l7policy.L7PolicyController()
@wsme_pecan.wsexpose(wtypes.text)
def get(self):
@ -34,4 +41,8 @@ class BaseV2Controller(base.BaseController):
class V2Controller(BaseV2Controller):
lbaas = BaseV2Controller()
lbaas = None
def __init__(self):
super(V2Controller, self).__init__()
self.lbaas = BaseV2Controller()

View File

@ -27,6 +27,7 @@ from octavia.api.v2.types import listener as listener_types
from octavia.common import constants
from octavia.common import data_models
from octavia.common import exceptions
from octavia.db import api as db_api
from octavia.db import prepare as db_prepare
from octavia.i18n import _LI
@ -111,6 +112,12 @@ class ListenersController(base.BaseController):
raise exceptions.NotFound(
resource=data_models.Pool._name(), id=pool_id)
def _reset_lb_status(self, session, lb_id):
# Setting LB back to active because this should be a recoverable error
self.repositories.load_balancer.update(
session, lb_id,
provisioning_status=constants.ACTIVE)
def _validate_listener(self, session, lb_id, listener_dict):
"""Validate listener for wrong protocol or duplicate listeners
@ -165,9 +172,13 @@ class ListenersController(base.BaseController):
db_listener.id)
self.handler.create(db_listener)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
with excutils.save_and_reraise_exception(
reraise=False), db_api.get_lock_session() as lock_session:
self._reset_lb_status(
lock_session, lb_id=db_listener.load_balancer_id)
# Listener now goes to ERROR
self.repositories.listener.update(
session, db_listener.id,
lock_session, db_listener.id,
provisioning_status=constants.ERROR)
db_listener = self._get_db_listener(session, db_listener.id)
result = self._convert_db_to_type(db_listener,
@ -222,9 +233,14 @@ class ListenersController(base.BaseController):
LOG.info(_LI("Sending Update of Listener %s to handler"), id)
self.handler.update(db_listener, listener)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
with excutils.save_and_reraise_exception(
reraise=False), db_api.get_lock_session() as lock_session:
self._reset_lb_status(
lock_session, lb_id=db_listener.load_balancer_id)
# Listener now goes to ERROR
self.repositories.listener.update(
context.session, id, provisioning_status=constants.ERROR)
lock_session, db_listener.id,
provisioning_status=constants.ERROR)
db_listener = self._get_db_listener(context.session, id)
result = self._convert_db_to_type(db_listener,
listener_types.ListenerResponse)
@ -245,9 +261,13 @@ class ListenersController(base.BaseController):
db_listener.id)
self.handler.delete(db_listener)
except Exception:
with excutils.save_and_reraise_exception(reraise=False):
with excutils.save_and_reraise_exception(
reraise=False), db_api.get_lock_session() as lock_session:
self._reset_lb_status(
lock_session, lb_id=db_listener.load_balancer_id)
# Listener now goes to ERROR
self.repositories.listener.update(
context.session, db_listener.id,
lock_session, db_listener.id,
provisioning_status=constants.ERROR)
db_listener = self.repositories.listener.get(
context.session, id=db_listener.id)

View File

@ -80,12 +80,6 @@ class BaseAPITest(base_db_test.OctaviaDBTestBase):
'octavia.db.repositories.Repositories.check_quota_met',
return_value=True)
self.app = self._make_app()
# For no apparent reason, the controller code for v2 uses a static
# handler mock (the one generated on the initial run) so we need to
# retrieve it so we use the "correct" mock instead of the one above
self.handler_mock_bug_workaround = getattr(
self.app.app.application.application.application.root,
'v2.0').handler
self.project_id = uuidutils.generate_uuid()
def reset_pecan():

View File

@ -273,12 +273,10 @@ class TestL7Policy(base.BaseAPITest):
self.post(self.L7POLICIES_PATH, self._build_body(l7policy), status=400)
def test_create_with_bad_handler(self):
(self.handler_mock_bug_workaround.
l7policy.create.side_effect) = Exception
self.handler_mock().l7policy.create.side_effect = Exception()
api_l7policy = self.create_l7policy(self.listener_id,
constants.L7POLICY_ACTION_REJECT,
).get(self.root_tag)
self.handler_mock_bug_workaround.l7policy.create.side_effect = None
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
l7policy_id=api_l7policy.get('id'),
@ -355,12 +353,10 @@ class TestL7Policy(base.BaseAPITest):
new_l7policy = {
'action': constants.L7POLICY_ACTION_REDIRECT_TO_URL,
'redirect_url': 'http://www.example.com'}
(self.handler_mock_bug_workaround.
l7policy.update.side_effect) = Exception
self.handler_mock().l7policy.update.side_effect = Exception()
self.put(self.L7POLICY_PATH.format(
l7policy_id=api_l7policy.get('id')),
self._build_body(new_l7policy))
self.handler_mock_bug_workaround.l7policy.update.side_effect = None
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
l7policy_id=api_l7policy.get('id'),
@ -445,11 +441,9 @@ class TestL7Policy(base.BaseAPITest):
self.assertIsNone(api_l7policy.pop('updated_at'))
self.assertIsNotNone(response.pop('updated_at'))
self.assertEqual(api_l7policy, response)
(self.handler_mock_bug_workaround.
l7policy.delete.side_effect) = Exception
self.handler_mock().l7policy.delete.side_effect = Exception()
self.delete(self.L7POLICY_PATH.format(
l7policy_id=api_l7policy.get('id')))
self.handler_mock_bug_workaround.l7policy.delete.side_effect = None
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
l7policy_id=api_l7policy.get('id'),

View File

@ -277,6 +277,53 @@ class TestListener(base.BaseAPITest):
self.assert_final_lb_statuses(self.lb_id)
self.assert_final_listener_statuses(self.lb_id, listener_api['id'])
def test_create_with_bad_handler(self):
self.handler_mock().listener.create.side_effect = Exception()
api_listener = self.create_listener(
constants.PROTOCOL_HTTP, 80,
self.lb_id).get(self.root_tag)
self.assert_correct_status(
lb_id=self.lb_id,
listener_id=api_listener.get('id'),
listener_prov_status=constants.ERROR,
listener_op_status=constants.OFFLINE)
def test_update_with_bad_handler(self):
api_listener = self.create_listener(
constants.PROTOCOL_HTTP, 80,
self.lb_id).get(self.root_tag)
self.set_lb_status(lb_id=self.lb_id)
new_listener = {'name': 'new_name'}
self.handler_mock().listener.update.side_effect = Exception()
self.put(self.LISTENER_PATH.format(listener_id=api_listener.get('id')),
self._build_body(new_listener))
self.assert_correct_status(
lb_id=self.lb_id,
listener_id=api_listener.get('id'),
listener_prov_status=constants.ERROR)
def test_delete_with_bad_handler(self):
api_listener = self.create_listener(
constants.PROTOCOL_HTTP, 80,
self.lb_id).get(self.root_tag)
self.set_lb_status(lb_id=self.lb_id)
# Set status to ACTIVE/ONLINE because set_lb_status did it in the db
api_listener['provisioning_status'] = constants.ACTIVE
api_listener['operating_status'] = constants.ONLINE
response = self.get(self.LISTENER_PATH.format(
listener_id=api_listener.get('id'))).json.get(self.root_tag)
self.assertIsNone(api_listener.pop('updated_at'))
self.assertIsNotNone(response.pop('updated_at'))
self.assertEqual(api_listener, response)
self.handler_mock().listener.delete.side_effect = Exception()
self.delete(self.LISTENER_PATH.format(
listener_id=api_listener.get('id')))
self.assert_correct_status(
lb_id=self.lb_id,
listener_id=api_listener.get('id'),
listener_prov_status=constants.ERROR)
def test_update(self):
tls_uuid = uuidutils.generate_uuid()
listener = self.create_listener(

View File

@ -505,6 +505,46 @@ class TestLoadBalancer(base.BaseAPITest):
path = self.LB_PATH.format(lb_id='bad_uuid')
self.delete(path, status=404)
def test_create_with_bad_handler(self):
self.handler_mock().load_balancer.create.side_effect = Exception()
api_lb = self.create_load_balancer(
uuidutils.generate_uuid()).get(self.root_tag)
self.assert_correct_status(
lb_id=api_lb.get('id'),
lb_prov_status=constants.ERROR,
lb_op_status=constants.OFFLINE)
def test_update_with_bad_handler(self):
api_lb = self.create_load_balancer(
uuidutils.generate_uuid()).get(self.root_tag)
self.set_lb_status(lb_id=api_lb.get('id'))
new_listener = {'name': 'new_name'}
self.handler_mock().load_balancer.update.side_effect = Exception()
self.put(self.LB_PATH.format(lb_id=api_lb.get('id')),
self._build_body(new_listener))
self.assert_correct_status(
lb_id=api_lb.get('id'),
lb_prov_status=constants.ERROR)
def test_delete_with_bad_handler(self):
api_lb = self.create_load_balancer(
uuidutils.generate_uuid()).get(self.root_tag)
self.set_lb_status(lb_id=api_lb.get('id'))
# Set status to ACTIVE/ONLINE because set_lb_status did it in the db
api_lb['provisioning_status'] = constants.ACTIVE
api_lb['operating_status'] = constants.ONLINE
response = self.get(self.LB_PATH.format(
lb_id=api_lb.get('id'))).json.get(self.root_tag)
self.assertIsNone(api_lb.pop('updated_at'))
self.assertIsNotNone(response.pop('updated_at'))
self.assertEqual(api_lb, response)
self.handler_mock().load_balancer.delete.side_effect = Exception()
self.delete(self.LB_PATH.format(lb_id=api_lb.get('id')))
self.assert_correct_status(
lb_id=api_lb.get('id'),
lb_prov_status=constants.ERROR)
class TestLoadBalancerGraph(base.BaseAPITest):

View File

@ -314,14 +314,12 @@ class TestPool(base.BaseAPITest):
self.post(self.POOLS_PATH, self._build_body(lb_pool), status=400)
def test_create_with_bad_handler(self):
self.handler_mock_bug_workaround.pool.create.side_effect = Exception()
self.handler_mock().pool.create.side_effect = Exception()
api_pool = self.create_pool(
self.lb_id,
constants.PROTOCOL_HTTP,
constants.LB_ALGORITHM_ROUND_ROBIN,
listener_id=self.listener_id).get(self.root_tag)
# This mock doesn't recycle properly so we have to do cleanup manually
self.handler_mock_bug_workaround.pool.create.side_effect = None
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=api_pool.get('id'),
@ -388,11 +386,9 @@ class TestPool(base.BaseAPITest):
listener_id=self.listener_id).get(self.root_tag)
self.set_lb_status(lb_id=self.lb_id)
new_pool = {'name': 'new_name'}
self.handler_mock_bug_workaround.pool.update.side_effect = Exception()
self.handler_mock().pool.update.side_effect = Exception()
self.put(self.POOL_PATH.format(pool_id=api_pool.get('id')),
self._build_body(new_pool))
# This mock doesn't recycle properly so we have to do cleanup manually
self.handler_mock_bug_workaround.pool.update.side_effect = None
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=api_pool.get('id'),
@ -458,10 +454,8 @@ class TestPool(base.BaseAPITest):
self.assertIsNone(api_pool.pop('updated_at'))
self.assertIsNotNone(response.pop('updated_at'))
self.assertEqual(api_pool, response)
self.handler_mock_bug_workaround.pool.delete.side_effect = Exception()
self.handler_mock().pool.delete.side_effect = Exception()
self.delete(self.POOL_PATH.format(pool_id=api_pool.get('id')))
# This mock doesn't recycle properly so we have to do cleanup manually
self.handler_mock_bug_workaround.pool.delete.side_effect = None
self.assert_correct_status(
lb_id=self.lb_id, listener_id=self.listener_id,
pool_id=api_pool.get('id'),