Octavia driver to honor Octavia API status codes

Currently the Octavia driver for neutron-lbaas is not honoring Octavia API
status codes. This can lead to a race condition where the status update
thread is behind the actual object status in Octavia.
This updates the driver to raise the appropriate status code to the
neutron API.

Change-Id: I3ec78578c941e4648d1fccfc92b349e3415015e6
Story: 2001257
Task: 5787
This commit is contained in:
Michael Johnson 2017-10-23 16:05:11 -07:00
parent dba5e27ede
commit cfea70bdc4
4 changed files with 117 additions and 0 deletions

View File

@ -54,3 +54,27 @@ class CertificateStorageException(TLSException):
class LoadbalancerReschedulingFailed(exceptions.Conflict):
message = _("Failed rescheduling loadbalancer %(loadbalancer_id)s: "
"no eligible lbaas agent found.")
class BadRequestException(exceptions.BadRequest):
message = "%(fault_string)s"
class ConflictException(exceptions.Conflict):
message = "%(fault_string)s"
class NotAuthorizedException(exceptions.NotAuthorized):
message = "%(fault_string)s"
class NotFoundException(exceptions.NotFound):
message = "%(fault_string)s"
class ServiceUnavailableException(exceptions.ServiceUnavailable):
message = "%(fault_string)s"
class UnknownException(exceptions.NeutronException):
message = "%(fault_string)s"

View File

@ -25,6 +25,7 @@ from oslo_utils import excutils
import requests
from neutron_lbaas._i18n import _
from neutron_lbaas.common import exceptions
from neutron_lbaas.common import keystone
from neutron_lbaas.drivers import driver_base
from neutron_lbaas.drivers.octavia import octavia_messaging_consumer
@ -144,6 +145,27 @@ class OctaviaRequest(object):
LOG.debug("Octavia Response Code: {0}".format(r.status_code))
LOG.debug("Octavia Response Body: {0}".format(r.content))
LOG.debug("Octavia Response Headers: {0}".format(r.headers))
# We need to raise Octavia errors up to neutron API.
try:
fault_string = jsonutils.loads(r.content)['faultstring']
except Exception:
fault_string = "Unknown Octavia error."
if r.status_code == 400:
raise exceptions.BadRequestException(fault_string=fault_string)
elif r.status_code == 401:
raise exceptions.NotAuthorizedException(fault_string=fault_string)
elif r.status_code == 403:
raise exceptions.NotAuthorizedException(fault_string=fault_string)
elif r.status_code == 404:
raise exceptions.NotFoundException(fault_string=fault_string)
elif r.status_code == 409:
raise exceptions.ConflictException(fault_string=fault_string)
elif r.status_code == 500:
raise exceptions.UnknownException(fault_string=fault_string)
elif r.status_code == 503:
raise exceptions.ServiceUnavailableException(
fault_string=fault_string)
if method != 'DELETE':
return r.json()

View File

@ -35,6 +35,7 @@ from oslo_utils import encodeutils
from neutron_lbaas import agent_scheduler as agent_scheduler_v2
import neutron_lbaas.common.cert_manager
from neutron_lbaas.common import exceptions
from neutron_lbaas.common.tls_utils import cert_parser
from neutron_lbaas.db.loadbalancer import loadbalancer_dbv2 as ldbv2
from neutron_lbaas.db.loadbalancer import models
@ -178,6 +179,13 @@ class LoadBalancerPluginv2(loadbalancerv2.LoadBalancerPluginBaseV2,
except (lbaas_agentschedulerv2.NoEligibleLbaasAgent,
lbaas_agentschedulerv2.NoActiveLbaasAgent) as no_agent:
raise no_agent
# Pass these exceptions through to neutron
except (exceptions.ConflictException,
exceptions.NotFoundException,
exceptions.NotAuthorizedException,
exceptions.BadRequestException,
exceptions.ServiceUnavailableException):
raise
except Exception as e:
LOG.exception("There was an error in the driver")
self._handle_driver_error(context, db_entity)

View File

@ -18,6 +18,7 @@ import mock
from neutron_lib import context
from oslo_config import cfg
from neutron_lbaas.common import exceptions
from neutron_lbaas.drivers.octavia import driver
from neutron_lbaas.services.loadbalancer import constants
from neutron_lbaas.services.loadbalancer import data_models
@ -625,3 +626,65 @@ class TestThreadedDriver(BaseOctaviaDriverTest):
delete=False,
lb_create=True)
self.assertEqual(expected_vip, self.lb.vip_address)
class TestOctaviaRequest(test_db_loadbalancerv2.LbaasPluginDbTestCase):
def setUp(self):
super(TestOctaviaRequest, self).setUp()
self.OctaviaRequestObj = driver.OctaviaRequest("TEST_URL",
"TEST_SESSION")
@mock.patch('requests.request')
def test_request(self, mock_requests):
mock_response = mock.MagicMock()
mock_response.status_code = 200
mock_response.content = "TEST"
mock_response.headers = "TEST headers"
mock_response.json.return_value = "TEST json"
mock_requests.return_value = mock_response
fake_header = {'X-Auth-Token': "TEST_TOKEN"}
result = self.OctaviaRequestObj.request("TEST_METHOD", "TEST_URL",
headers=fake_header)
self.assertEqual("TEST json", result)
@mock.patch('requests.request')
def _test_request_Octavia(self, status_code, exception, mock_requests):
mock_response = mock.MagicMock()
mock_response.status_code = status_code
mock_response.content = '{"faultstring": "FAILED"}'
mock_response.headers = "TEST headers"
mock_requests.return_value = mock_response
fake_header = {'X-Auth-Token': "TEST_TOKEN"}
self.assertRaisesRegex(exception,
"FAILED",
self.OctaviaRequestObj.request,
"TEST_METHOD", "TEST_URL", headers=fake_header)
mock_response.json.assert_not_called()
def test_request_Octavia_400(self):
self._test_request_Octavia(400, exceptions.BadRequestException)
def test_request_Octavia_401(self):
self._test_request_Octavia(401, exceptions.NotAuthorizedException)
def test_request_Octavia_403(self):
self._test_request_Octavia(403, exceptions.NotAuthorizedException)
def test_request_Octavia_404(self):
self._test_request_Octavia(404, exceptions.NotFoundException)
def test_request_Octavia_409(self):
self._test_request_Octavia(409, exceptions.ConflictException)
def test_request_Octavia_500(self):
self._test_request_Octavia(500, exceptions.UnknownException)
def test_request_Octavia_503(self):
self._test_request_Octavia(503, exceptions.ServiceUnavailableException)