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:
parent
af6656fc4c
commit
de3ad7abac
@ -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::
|
||||
|
@ -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
108
keystone/common/sql/nova.py
Normal 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)
|
136
tests/test_migrate_nova_auth.py
Normal file
136
tests/test_migrate_nova_auth.py
Normal 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))
|
Loading…
x
Reference in New Issue
Block a user