Add migration path for Nova auth

* Adds keystone-manage command import_nova_auth
* Document migration path in docs/source/configuration.rst

Change-Id: I35bc20686bfed3f13162e278cf9a1713c78fad1e
This commit is contained in:
Brian Waldon 2012-02-18 23:24:44 -08:00
parent af6656fc4c
commit de3ad7abac
4 changed files with 311 additions and 1 deletions

View File

@ -123,7 +123,7 @@ Use the following command to import your old data::
Specify db_url as the connection string that was present in your old
keystone.conf file.
Step 3: Import your legacy service catalog
Step 4: Import your legacy service catalog
------------------------------------------
While the older keystone stored the service catalog in the database,
the updated version configures the service catalog using a template file.
@ -139,6 +139,53 @@ To import your legacy catalog, run this command::
After executing this command, you will need to restart the keystone service to
see your changes.
Migrating from Nova Auth
========================
Migration of users, projects (aka tenants), roles and EC2 credentials
is supported for the Diablo and Essex releases of Nova. To migrate your auth
data from Nova, use the following steps:
Step 1: Export your data from Nova
----------------------------------
Use the following command to export your data fron Nova::
nova-manage export auth > /path/to/dump
It is important to redirect the output to a file so it can be imported
in a later step.
Step 2: db_sync your new, empty database
----------------------------------------
Run the following command to configure the most recent schema in your new
keystone installation::
keystone-manage db_sync
Step 3: Import your data to Keystone
------------------------------------
To import your Nova auth data from a dump file created with nova-manage,
run this command::
keystone-manage import_nova_auth [dump_file, e.g. /path/to/dump]
.. note::
Users are added to Keystone with the user id from Nova as the user name.
Nova's projects are imported with the project id as the tenant name. The
password used to authenticate a user in Keystone will be the api key
(also EC2 access key) used in Nova. Users also lose any administrative
privileges they had in Nova. The necessary admin role must be explicitly
re-assigned to each user.
.. note::
Users in Nova's auth system have a single set of EC2 credentials that
works with all projects (tenants) that user can access. In Keystone, these
credentials are scoped to a single user/tenant pair. In order to use the
same secret keys from Nova, you must prefix each corresponding access key
with the id of the project used in Nova. For example, if you had access
to the 'Beta' project in your Nova installation with the access/secret
keys 'XXX'/'YYY', you should use 'Beta:XXX'/'YYY' in Keystone. These
credentials are active once your migration is complete.
Initializing Keystone
=====================
@ -148,6 +195,7 @@ through the normal REST api. At the moment, the following calls are supported:
* ``db_sync``: Sync the database.
* ``import_legacy``: Import a legacy (pre-essex) version of the db.
* ``export_legacy_catalog``: Export service catalog from a legacy (pre-essex) db.
* ``import_nova_auth``: Load auth data from a dump created with keystone-manage.
Generally, the following is the first step after a source installation::

View File

@ -90,9 +90,27 @@ class ExportLegacyCatalog(BaseApp):
print '\n'.join(migration.dump_catalog())
class ImportNovaAuth(BaseApp):
"""Import a dump of nova auth data into keystone."""
name = 'import_nova_auth'
def __init__(self, *args, **kw):
super(ImportNovaAuth, self).__init__(*args, **kw)
def main(self):
from keystone.common.sql import nova
if len(self.argv) < 2:
return self.missing_param('dump_file')
dump_file = self.argv[1]
dump_data = json.loads(open(dump_file).read())
nova.import_auth(dump_data)
CMDS = {'db_sync': DbSync,
'import_legacy': ImportLegacy,
'export_legacy_catalog': ExportLegacyCatalog,
'import_nova_auth': ImportNovaAuth,
}

108
keystone/common/sql/nova.py Normal file
View File

@ -0,0 +1,108 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
"""Export data from Nova database and import through Identity Service."""
import logging
import uuid
from keystone.contrib.ec2.backends import sql as ec2_sql
from keystone.identity.backends import sql as identity_sql
logger = logging.getLogger('keystone.common.sql.nova')
def import_auth(data):
identity_api = identity_sql.Identity()
tenant_map = _create_tenants(identity_api, data['tenants'])
user_map = _create_users(identity_api, data['users'])
_create_memberships(identity_api, data['user_tenant_list'],
user_map, tenant_map)
role_map = _create_roles(identity_api, data['roles'])
_assign_roles(identity_api, data['role_user_tenant_list'],
role_map, user_map, tenant_map)
ec2_api = ec2_sql.Ec2()
ec2_creds = data['ec2_credentials']
_create_ec2_creds(ec2_api, identity_api, ec2_creds, user_map)
def _generate_uuid():
return uuid.uuid4().hex
def _create_tenants(api, tenants):
tenant_map = {}
for tenant in tenants:
tenant_dict = {
'id': _generate_uuid(),
'name': tenant['id'],
'description': tenant['description'],
'enabled': True,
}
tenant_map[tenant['id']] = tenant_dict['id']
logger.debug('Create tenant %s' % tenant_dict)
api.create_tenant(tenant_dict['id'], tenant_dict)
return tenant_map
def _create_users(api, users):
user_map = {}
for user in users:
user_dict = {
'id': _generate_uuid(),
'name': user['id'],
'email': '',
'password': user['password'],
'enabled': True,
}
user_map[user['id']] = user_dict['id']
logger.debug('Create user %s' % user_dict)
api.create_user(user_dict['id'], user_dict)
return user_map
def _create_memberships(api, memberships, user_map, tenant_map):
for membership in memberships:
user_id = user_map[membership['user_id']]
tenant_id = tenant_map[membership['tenant_id']]
logger.debug('Add user %s to tenant %s' % (user_id, tenant_id))
api.add_user_to_tenant(tenant_id, user_id)
def _create_roles(api, roles):
role_map = {}
for role in roles:
role_dict = {
'id': _generate_uuid(),
'name': role,
}
role_map[role] = role_dict['id']
logger.debug('Create role %s' % role_dict)
api.create_role(role_dict['id'], role_dict)
return role_map
def _assign_roles(api, assignments, role_map, user_map, tenant_map):
for assignment in assignments:
role_id = role_map[assignment['role']]
user_id = user_map[assignment['user_id']]
tenant_id = tenant_map[assignment['tenant_id']]
logger.debug('Assign role %s to user %s on tenant %s' %
(role_id, user_id, tenant_id))
api.add_role_to_user_and_tenant(user_id, tenant_id, role_id)
def _create_ec2_creds(ec2_api, identity_api, ec2_creds, user_map):
for ec2_cred in ec2_creds:
user_id = user_map[ec2_cred['user_id']]
for tenant_id in identity_api.get_tenants_for_user(user_id):
cred_dict = {
'access': '%s:%s' % (tenant_id, ec2_cred['access_key']),
'secret': ec2_cred['secret_key'],
'user_id': user_id,
'tenant_id': tenant_id,
}
logger.debug('Creating ec2 cred for user %s and tenant %s' %
(user_id, tenant_id))
ec2_api.create_credential(None, cred_dict)

View File

@ -0,0 +1,136 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack LLC
#
# 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.
from keystone.common.sql import nova
from keystone.common.sql import util as sql_util
from keystone import config
from keystone.contrib.ec2.backends import sql as ec2_sql
from keystone.identity.backends import sql as identity_sql
from keystone import test
CONF = config.CONF
FIXTURE = {
'users': [
{'id': 'user1', 'name': 'uname1', 'password': 'acc1'},
{'id': 'user4', 'name': 'uname4', 'password': 'acc1'},
{'id': 'user2', 'name': 'uname2', 'password': 'acc2'},
{'id': 'user3', 'name': 'uname3', 'password': 'acc3'},
],
'roles': ['role1', 'role2', 'role3'],
'role_user_tenant_list': [
{'user_id': 'user1', 'role': 'role1', 'tenant_id': 'proj1'},
{'user_id': 'user1', 'role': 'role2', 'tenant_id': 'proj1'},
{'user_id': 'user4', 'role': 'role1', 'tenant_id': 'proj4'},
{'user_id': 'user2', 'role': 'role1', 'tenant_id': 'proj1'},
{'user_id': 'user2', 'role': 'role1', 'tenant_id': 'proj2'},
{'user_id': 'user2', 'role': 'role2', 'tenant_id': 'proj2'},
{'user_id': 'user3', 'role': 'role3', 'tenant_id': 'proj1'},
],
'user_tenant_list': [
{'tenant_id': 'proj1', 'user_id': 'user1'},
{'tenant_id': 'proj4', 'user_id': 'user4'},
{'tenant_id': 'proj1', 'user_id': 'user2'},
{'tenant_id': 'proj2', 'user_id': 'user2'},
{'tenant_id': 'proj1', 'user_id': 'user3'},
],
'ec2_credentials': [
{'access_key': 'acc1', 'secret_key': 'sec1', 'user_id': 'user1'},
{'access_key': 'acc4', 'secret_key': 'sec4', 'user_id': 'user4'},
{'access_key': 'acc2', 'secret_key': 'sec2', 'user_id': 'user2'},
{'access_key': 'acc3', 'secret_key': 'sec3', 'user_id': 'user3'},
],
'tenants': [
{'description': 'desc1', 'id': 'proj1', 'name': 'pname1'},
{'description': 'desc4', 'id': 'proj4', 'name': 'pname4'},
{'description': 'desc2', 'id': 'proj2', 'name': 'pname2'},
],
}
class MigrateNovaAuth(test.TestCase):
def setUp(self):
super(MigrateNovaAuth, self).setUp()
CONF(config_files=[test.etcdir('keystone.conf'),
test.testsdir('test_overrides.conf'),
test.testsdir('backend_sql.conf')])
sql_util.setup_test_database()
self.identity_api = identity_sql.Identity()
self.ec2_api = ec2_sql.Ec2()
def test_import(self):
nova.import_auth(FIXTURE)
users = {}
for user in ['user1', 'user2', 'user3', 'user4']:
users[user] = self.identity_api.get_user_by_name(user)
tenants = {}
for tenant in ['proj1', 'proj2', 'proj4']:
tenants[tenant] = self.identity_api.get_tenant_by_name(tenant)
membership_map = {
'user1': ['proj1'],
'user2': ['proj1', 'proj2'],
'user3': ['proj1'],
'user4': ['proj4'],
}
for (old_user, old_tenants) in membership_map.iteritems():
user = users[old_user]
membership = self.identity_api.get_tenants_for_user(user['id'])
expected = [tenants[t]['id'] for t in old_tenants]
self.assertEqual(set(expected), set(membership))
for tenant_id in membership:
password = None
for _user in FIXTURE['users']:
if _user['id'] == old_user:
password = _user['password']
self.identity_api.authenticate(user['id'], tenant_id, password)
for ec2_cred in FIXTURE['ec2_credentials']:
user_id = users[ec2_cred['user_id']]['id']
for tenant_id in self.identity_api.get_tenants_for_user(user_id):
access = '%s:%s' % (tenant_id, ec2_cred['access_key'])
cred = self.ec2_api.get_credential(access)
actual = cred['secret']
expected = ec2_cred['secret_key']
self.assertEqual(expected, actual)
roles = self.identity_api.list_roles()
role_names = set([role['name'] for role in roles])
self.assertEqual(role_names, set(['role2', 'role1', 'role3']))
assignment_map = {
'user1': {'proj1': ['role1', 'role2']},
'user2': {'proj1': ['role1'], 'proj2': ['role1', 'role2']},
'user3': {'proj1': ['role3']},
'user4': {'proj4': ['role1']},
}
for (old_user, old_tenant_map) in assignment_map.iteritems():
tenant_names = ['proj1', 'proj2', 'proj4']
for tenant_name in tenant_names:
user = users[old_user]
tenant = tenants[tenant_name]
roles = self.identity_api.get_roles_for_user_and_tenant(
user['id'], tenant['id'])
actual = [self.identity_api.get_role(role_id)['name']
for role_id in roles]
expected = old_tenant_map.get(tenant_name, [])
self.assertEqual(set(actual), set(expected))