summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/source/configuration.rst50
-rw-r--r--keystone/cli.py18
-rw-r--r--keystone/common/sql/nova.py108
-rw-r--r--tests/test_migrate_nova_auth.py136
4 files changed, 311 insertions, 1 deletions
diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst
index 22f3748b..a225c4ab 100644
--- a/docs/source/configuration.rst
+++ b/docs/source/configuration.rst
@@ -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::
diff --git a/keystone/cli.py b/keystone/cli.py
index 039fc92c..383c4497 100644
--- a/keystone/cli.py
+++ b/keystone/cli.py
@@ -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,
}
diff --git a/keystone/common/sql/nova.py b/keystone/common/sql/nova.py
new file mode 100644
index 00000000..bf97669f
--- /dev/null
+++ b/keystone/common/sql/nova.py
@@ -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)
diff --git a/tests/test_migrate_nova_auth.py b/tests/test_migrate_nova_auth.py
new file mode 100644
index 00000000..1be59b17
--- /dev/null
+++ b/tests/test_migrate_nova_auth.py
@@ -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))