Merge "Add online migration to move keypairs from main to API database"
This commit is contained in:
commit
7b35aa97d8
|
@ -82,6 +82,7 @@ from nova.i18n import _
|
|||
from nova import objects
|
||||
from nova.objects import flavor as flavor_obj
|
||||
from nova.objects import instance as instance_obj
|
||||
from nova.objects import keypair as keypair_obj
|
||||
from nova.objects import request_spec
|
||||
from nova import quota
|
||||
from nova import rpc
|
||||
|
@ -728,6 +729,7 @@ class DbCommands(object):
|
|||
flavor_obj.migrate_flavor_reset_autoincrement,
|
||||
instance_obj.migrate_instance_keypairs,
|
||||
request_spec.migrate_instances_add_request_spec,
|
||||
keypair_obj.migrate_keypairs_to_api_db,
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
|
|
|
@ -13,18 +13,22 @@
|
|||
# under the License.
|
||||
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import versionutils
|
||||
|
||||
from nova import db
|
||||
from nova.db.sqlalchemy import api as db_api
|
||||
from nova.db.sqlalchemy import api_models
|
||||
from nova.db.sqlalchemy import models as main_models
|
||||
from nova import exception
|
||||
from nova.i18n import _LE
|
||||
from nova import objects
|
||||
from nova.objects import base
|
||||
from nova.objects import fields
|
||||
|
||||
KEYPAIR_TYPE_SSH = 'ssh'
|
||||
KEYPAIR_TYPE_X509 = 'x509'
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@db_api.api_context_manager.reader
|
||||
|
@ -155,6 +159,9 @@ class KeyPair(base.NovaPersistentObject, base.NovaObject,
|
|||
except exception.KeypairNotFound:
|
||||
pass
|
||||
|
||||
self._create()
|
||||
|
||||
def _create(self):
|
||||
updates = self.obj_get_changes()
|
||||
db_keypair = self._create_in_db(self._context, updates)
|
||||
self._from_db_object(self._context, self, db_keypair)
|
||||
|
@ -198,3 +205,49 @@ class KeyPairList(base.ObjectListBase, base.NovaObject):
|
|||
def get_count_by_user(cls, context, user_id):
|
||||
return (cls._get_count_from_db(context, user_id) +
|
||||
db.key_pair_count_by_user(context, user_id))
|
||||
|
||||
|
||||
@db_api.main_context_manager.reader
|
||||
def _count_unmigrated_instances(context):
|
||||
return context.session.query(main_models.InstanceExtra).\
|
||||
filter_by(keypairs=None).\
|
||||
filter_by(deleted=0).\
|
||||
count()
|
||||
|
||||
|
||||
@db_api.main_context_manager.reader
|
||||
def _get_main_keypairs(context, limit):
|
||||
return context.session.query(main_models.KeyPair).\
|
||||
filter_by(deleted=0).\
|
||||
limit(limit).\
|
||||
all()
|
||||
|
||||
|
||||
def migrate_keypairs_to_api_db(context, count):
|
||||
bad_instances = _count_unmigrated_instances(context)
|
||||
if bad_instances:
|
||||
LOG.error(_LE('Some instances are still missing keypair '
|
||||
'information. Unable to run keypair migration '
|
||||
'at this time.'))
|
||||
return 0, 0
|
||||
|
||||
main_keypairs = _get_main_keypairs(context, count)
|
||||
done = 0
|
||||
for db_keypair in main_keypairs:
|
||||
kp = objects.KeyPair(context=context,
|
||||
user_id=db_keypair.user_id,
|
||||
name=db_keypair.name,
|
||||
fingerprint=db_keypair.fingerprint,
|
||||
public_key=db_keypair.public_key,
|
||||
type=db_keypair.type)
|
||||
try:
|
||||
kp._create()
|
||||
except exception.KeyPairExists:
|
||||
# NOTE(danms): If this got created somehow in the API DB,
|
||||
# then it's newer and we just continue on to destroy the
|
||||
# old one in the cell DB.
|
||||
pass
|
||||
db_api.key_pair_destroy(context, db_keypair.user_id, db_keypair.name)
|
||||
done += 1
|
||||
|
||||
return len(main_keypairs), done
|
||||
|
|
|
@ -134,3 +134,46 @@ class KeyPairObjectTestCase(test.NoDBTestCase):
|
|||
count = objects.KeyPairList.get_count_by_user(self.context,
|
||||
self.context.user_id)
|
||||
self.assertEqual(2, count)
|
||||
|
||||
def test_migrate_keypairs(self):
|
||||
self._api_kp(name='apikey')
|
||||
self._main_kp(name='mainkey1')
|
||||
self._main_kp(name='mainkey2')
|
||||
self._main_kp(name='mainkey3')
|
||||
total, done = keypair.migrate_keypairs_to_api_db(self.context, 2)
|
||||
self.assertEqual(2, total)
|
||||
self.assertEqual(2, done)
|
||||
|
||||
# NOTE(danms): This only fetches from the API DB
|
||||
api_keys = objects.KeyPairList._get_from_db(self.context,
|
||||
self.context.user_id)
|
||||
self.assertEqual(3, len(api_keys))
|
||||
|
||||
# NOTE(danms): This only fetches from the main DB
|
||||
main_keys = db_api.key_pair_get_all_by_user(self.context,
|
||||
self.context.user_id)
|
||||
self.assertEqual(1, len(main_keys))
|
||||
|
||||
self.assertEqual((1, 1),
|
||||
keypair.migrate_keypairs_to_api_db(self.context, 100))
|
||||
self.assertEqual((0, 0),
|
||||
keypair.migrate_keypairs_to_api_db(self.context, 100))
|
||||
|
||||
def test_migrate_keypairs_bails_on_unmigrated_instances(self):
|
||||
objects.Instance(context=self.context, user_id=self.context.user_id,
|
||||
project_id=self.context.project_id).create()
|
||||
self._api_kp(name='apikey')
|
||||
self._main_kp(name='mainkey1')
|
||||
total, done = keypair.migrate_keypairs_to_api_db(self.context, 100)
|
||||
self.assertEqual(0, total)
|
||||
self.assertEqual(0, done)
|
||||
|
||||
def test_migrate_keypairs_skips_existing(self):
|
||||
self._api_kp(name='mykey')
|
||||
self._main_kp(name='mykey')
|
||||
total, done = keypair.migrate_keypairs_to_api_db(self.context, 100)
|
||||
self.assertEqual(1, total)
|
||||
self.assertEqual(1, done)
|
||||
total, done = keypair.migrate_keypairs_to_api_db(self.context, 100)
|
||||
self.assertEqual(0, total)
|
||||
self.assertEqual(0, done)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
upgrade:
|
||||
- Keypairs have been moved to the API database, using an online
|
||||
data migration. During the first phase of the migration, instances
|
||||
will be given local storage of their key, after which keypairs will
|
||||
be moved to the API database.
|
Loading…
Reference in New Issue