Merge "Correctly handle integrity errors on MySQL 8.x"

This commit is contained in:
Zuul 2021-04-20 14:19:44 +00:00 committed by Gerrit Code Review
commit 91b0ff8849
2 changed files with 93 additions and 3 deletions

View File

@ -105,11 +105,26 @@ def create_resource_provider(req):
# Whether exc.columns has one or two entries (in the event
# of both fields being duplicates) appears to be database
# dependent, so going with the complete solution here.
duplicate = ', '.join(
['%s: %s' % (column, data[column]) for column in exc.columns])
duplicates = []
for column in exc.columns:
# For MySQL, this is error 1062:
#
# Duplicate entry '%s' for key %d
#
# The 'key' value is captured in 'DBDuplicateEntry.columns'.
# Despite the name, this isn't always a column name. While MySQL
# 5.x does indeed use the name of the column, 8.x uses the name of
# the constraint. oslo.db should probably fix this, but until that
# happens we need to handle both cases
if column == 'uniq_resource_providers0uuid':
duplicates.append(f'uuid: {data["uuid"]}')
elif column == 'uniq_resource_providers0name':
duplicates.append(f'name: {data["name"]}')
else:
duplicates.append(f'{column}: {data[column]}')
raise webob.exc.HTTPConflict(
'Conflicting resource provider %(duplicate)s already exists.' %
{'duplicate': duplicate},
{'duplicate': ', '.join(duplicates)},
comment=errors.DUPLICATE_NAME)
except exception.ObjectActionError as exc:
raise webob.exc.HTTPBadRequest(

View File

@ -0,0 +1,75 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Unit tests for code in the resource provider handler that gabbi isn't covering.
"""
from unittest import mock
import microversion_parse
from oslo_db import exception as db_exc
import webob
from placement import context
from placement.handlers import resource_provider
from placement.tests.unit import base
class TestAggregateHandlerErrors(base.ContextTestCase):
@mock.patch('placement.context.RequestContext.can', new=mock.Mock())
def _test_duplicate_error_parsing_mysql(self, key):
fake_context = context.RequestContext(
user_id='fake', project_id='fake')
req = webob.Request.blank(
'/resource_providers',
method='POST',
content_type='application/json')
req.body = b'{"name": "foobar"}'
req.environ['placement.context'] = fake_context
parse_version = microversion_parse.parse_version_string
microversion = parse_version('1.15')
microversion.max_version = parse_version('9.99')
microversion.min_version = parse_version('1.0')
req.environ['placement.microversion'] = microversion
with mock.patch(
'placement.objects.resource_provider.ResourceProvider.create',
side_effect=db_exc.DBDuplicateEntry(columns=[key]),
):
response = req.get_response(
resource_provider.create_resource_provider)
self.assertEqual('409 Conflict', response.status)
self.assertIn(
'Conflicting resource provider name: foobar already exists.',
response.text)
def test_duplicate_error_parsing_mysql_5x(self):
"""Ensure we parse the correct column on MySQL 5.x.
On MySQL 5.x, DBDuplicateEntry.columns will contain the name of the
column causing the integrity error.
"""
self._test_duplicate_error_parsing_mysql('name')
def test_duplicate_error_parsing_mysql_8x(self):
"""Ensure we parse the correct column on MySQL 5.x.
On MySQL 5.x, DBDuplicateEntry.columns will contain the name of the
constraint causing the integrity error.
"""
self._test_duplicate_error_parsing_mysql(
'uniq_resource_providers0name')