Handle unicode when dealing with duplicate flavors during online migrations

In python 2.7 you can't cast unicode to str and you'll get a
UnicodeEncodeError if you try. Since our exception messages are
translated, they can contain unicode and if we hit a duplicate flavor
exception while performing online flavor migrations it will break the
migration and block any further migrations until resolved - which would
require manual intervention.

This patch fixes the bug so that we use six.text_type instead of str
for logging the duplicate exception and adds a test to exhibit the bug
and prove it's fixed.

On a side note, it's curious that we don't delete the duplicate flavor
from the main database during the online data migration, but it's also
strange that you'd have duplicates in the API database because the
Flavor.create() checks to see if all flavors have been migrated from
the main DB to the API DB and if not you can't create new flavors in
the API DB - so the only way to get the duplicates is either by inserting
them into the API DB manually or perhaps some other kind of race issue.
Anyway, that's not dealt with here.

Change-Id: I3bdb1a8ca72c3e72ddc3bc5102cff8df18148617
Partial-Bug: #1653261
This commit is contained in:
Matt Riedemann 2016-12-30 12:27:48 -05:00
parent 641f5f4529
commit eda54b1ed7
2 changed files with 30 additions and 1 deletions

View File

@ -15,6 +15,7 @@
from oslo_db import exception as db_exc
from oslo_db.sqlalchemy import utils as sqlalchemyutils
from oslo_log import log as logging
import six
from sqlalchemy import or_
from sqlalchemy.orm import joinedload
from sqlalchemy.sql.expression import asc
@ -723,7 +724,7 @@ def migrate_flavors(ctxt, count, hard_delete=False):
LOG.warning(_LW('Flavor id %(id)i disappeared during migration'),
{'id': flavor_id})
except (exception.FlavorExists, exception.FlavorIdExists) as e:
LOG.error(str(e))
LOG.error(six.text_type(e))
return count_all, count_hit

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
from nova import context
from nova import db
from nova.db.sqlalchemy import api as db_api
@ -291,3 +293,29 @@ class FlavorMigrationTestCase(test.NoDBTestCase):
self.context, 0)
self.assertEqual(0, match)
self.assertEqual(0, done)
@mock.patch('nova.objects.flavor.LOG.error')
def test_migrate_flavors_duplicate_unicode(self, mock_log_error):
"""Tests that we handle a duplicate flavor when migrating and that
we handle when the exception message is in unicode.
"""
# First create a flavor that will be migrated from main to API DB.
main_flavor = _create_main_flavor(self.context)
# Now create that same flavor in the API DB.
del main_flavor['id']
api_flavor = ForcedFlavor(self.context, **main_flavor)
api_flavor.create()
# Now let's run the online data migration which will fail to create
# a duplicate flavor in the API database and will raise FlavorIdExists
# or FlavorExists which we want to modify to have a unicode message.
with mock.patch.object(exception.FlavorIdExists, 'msg_fmt',
u'\xF0\x9F\x92\xA9'):
with mock.patch.object(exception.FlavorExists, 'msg_fmt',
u'\xF0\x9F\x92\xA9'):
match, done = flavor_obj.migrate_flavors(self.context, 50)
# we found one
self.assertEqual(1, match)
# but we didn't migrate it
self.assertEqual(0, done)
# and we logged an error for the duplicate flavor
mock_log_error.assert_called()