From 64c94e74c905a93d12193d333b697856a3567a72 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Fri, 26 Feb 2016 12:24:47 +1100 Subject: [PATCH] Add identity endpoint creation to bootstrap To enable the bootstrapped admin user to perform identity operations we must also bootstrap the identity endpoint. Allow specifying service name, endpoint urls and region information to the bootstrap command. Change-Id: Ie78c61ecf1e5f787dd2528b887c1642fd8d457ff Closes-Bug: #1550057 --- keystone/cmd/cli.py | 122 ++++++++++++++++++++++++++++++++ keystone/tests/unit/test_cli.py | 81 +++++++++++++++++++++ 2 files changed, 203 insertions(+) diff --git a/keystone/cmd/cli.py b/keystone/cmd/cli.py index eaf1ffe1b1..0beaca8c9c 100644 --- a/keystone/cmd/cli.py +++ b/keystone/cmd/cli.py @@ -63,10 +63,17 @@ class BootStrap(BaseApp): self.load_backends() self.project_id = uuid.uuid4().hex self.role_id = uuid.uuid4().hex + self.service_id = None + self.service_name = None self.username = None self.project_name = None self.role_name = None self.password = None + self.public_url = None + self.internal_url = None + self.admin_url = None + self.region_id = None + self.endpoints = {} @classmethod def add_argument_parser(cls, subparsers): @@ -88,6 +95,31 @@ class BootStrap(BaseApp): metavar='OS_BOOTSTRAP_ROLE_NAME', help=('The initial role-name created during the ' 'keystone bootstrap process.')) + parser.add_argument('--bootstrap-service-name', default='keystone', + metavar='OS_BOOTSTRAP_SERVICE_NAME', + help=('The initial name for the initial identity ' + 'service created during the keystone ' + 'bootstrap process.')) + parser.add_argument('--bootstrap-admin-url', + metavar='OS_BOOTSTRAP_ADMIN_URL', + help=('The initial identity admin url created ' + 'during the keystone bootstrap process. ' + 'e.g. http://127.0.0.1:35357/v2.0')) + parser.add_argument('--bootstrap-public-url', + metavar='OS_BOOTSTRAP_PUBLIC_URL', + help=('The initial identity public url created ' + 'during the keystone bootstrap process. ' + 'e.g. http://127.0.0.1:5000/v2.0')) + parser.add_argument('--bootstrap-internal-url', + metavar='OS_BOOTSTRAP_INTERNAL_URL', + help=('The initial identity internal url created ' + 'during the keystone bootstrap process. ' + 'e.g. http://127.0.0.1:5000/v2.0')) + parser.add_argument('--bootstrap-region-id', + metavar='OS_BOOTSTRAP_REGION_ID', + help=('The initial region_id endpoints will be ' + 'placed in during the keystone bootstrap ' + 'process.')) return parser def load_backends(self): @@ -95,6 +127,7 @@ class BootStrap(BaseApp): self.resource_manager = drivers['resource_api'] self.identity_manager = drivers['identity_api'] self.assignment_manager = drivers['assignment_api'] + self.catalog_manager = drivers['catalog_api'] self.role_manager = drivers['role_api'] def _get_config(self): @@ -110,6 +143,21 @@ class BootStrap(BaseApp): self.password = ( os.environ.get('OS_BOOTSTRAP_PASSWORD') or CONF.command.bootstrap_password) + self.service_name = ( + os.environ.get('OS_BOOTSTRAP_SERVICE_NAME') or + CONF.command.bootstrap_service_name) + self.admin_url = ( + os.environ.get('OS_BOOTSTRAP_ADMIN_URL') or + CONF.command.bootstrap_admin_url) + self.public_url = ( + os.environ.get('OS_BOOTSTRAP_PUBLIC_URL') or + CONF.command.bootstrap_public_url) + self.internal_url = ( + os.environ.get('OS_BOOTSTRAP_INTERNAL_URL') or + CONF.command.bootstrap_internal_url) + self.region_id = ( + os.environ.get('OS_BOOTSTRAP_REGION_ID') or + CONF.command.bootstrap_region_id) def do_bootstrap(self): """Perform the bootstrap actions. @@ -215,6 +263,80 @@ class BootStrap(BaseApp): 'role': self.role_name, 'project': self.project_name}) + if self.region_id: + try: + self.catalog_manager.create_region( + region_ref={'id': self.region_id} + ) + LOG.info(_LI('Created Region %s'), self.region_id) + except exception.Conflict: + LOG.info(_LI('Region %s exists, skipping creation.'), + self.region_id) + + if self.public_url or self.admin_url or self.internal_url: + hints = driver_hints.Hints() + hints.add_filter('type', 'identity') + services = self.catalog_manager.list_services(hints) + + if services: + service_ref = services[0] + + hints = driver_hints.Hints() + hints.add_filter('service_id', service_ref['id']) + if self.region_id: + hints.add_filter('region_id', self.region_id) + + endpoints = self.catalog_manager.list_endpoints(hints) + else: + service_ref = {'id': uuid.uuid4().hex, + 'name': self.service_name, + 'type': 'identity', + 'enabled': True} + + self.catalog_manager.create_service( + service_id=service_ref['id'], + service_ref=service_ref) + + endpoints = [] + + self.service_id = service_ref['id'] + + available_interfaces = {e['interface']: e for e in endpoints} + expected_endpoints = {'public': self.public_url, + 'internal': self.internal_url, + 'admin': self.admin_url} + + for interface, url in expected_endpoints.items(): + if not url: + # not specified to bootstrap command + continue + + try: + endpoint_ref = available_interfaces[interface] + except KeyError: + endpoint_ref = {'id': uuid.uuid4().hex, + 'interface': interface, + 'url': url, + 'service_id': self.service_id, + 'enabled': True} + + if self.region_id: + endpoint_ref['region_id'] = self.region_id + + self.catalog_manager.create_endpoint( + endpoint_id=endpoint_ref['id'], + endpoint_ref=endpoint_ref) + + LOG.info(_LI('Created %(interface)s endpoint %(url)s'), + {'interface': interface, 'url': url}) + else: + # NOTE(jamielennox): electing not to update existing + # endpoints here. There may be call to do so in future. + LOG.info(_LI('Skipping %s endpoint as already created'), + interface) + + self.endpoints[interface] = endpoint_ref['id'] + @classmethod def main(cls): klass = cls() diff --git a/keystone/tests/unit/test_cli.py b/keystone/tests/unit/test_cli.py index 114a9df495..06f2e17275 100644 --- a/keystone/tests/unit/test_cli.py +++ b/keystone/tests/unit/test_cli.py @@ -87,6 +87,30 @@ class CliBootStrapTestCase(unit.SQLDriverOverrides, unit.TestCase): user['id'], bootstrap.password) + if bootstrap.region_id: + region = bootstrap.catalog_manager.get_region(bootstrap.region_id) + self.assertEqual(self.region_id, region['id']) + + if bootstrap.service_id: + svc = bootstrap.catalog_manager.get_service(bootstrap.service_id) + self.assertEqual(self.service_name, svc['name']) + + self.assertEqual(set(['admin', 'public', 'internal']), + set(bootstrap.endpoints)) + + urls = {'public': self.public_url, + 'internal': self.internal_url, + 'admin': self.admin_url} + + for interface, url in urls.items(): + endpoint_id = bootstrap.endpoints[interface] + endpoint = bootstrap.catalog_manager.get_endpoint(endpoint_id) + + self.assertEqual(self.region_id, endpoint['region_id']) + self.assertEqual(url, endpoint['url']) + self.assertEqual(svc['id'], endpoint['service_id']) + self.assertEqual(interface, endpoint['interface']) + def test_bootstrap_is_idempotent(self): # NOTE(morganfainberg): Ensure we can run bootstrap multiple times # without erroring. @@ -107,6 +131,11 @@ class CliBootStrapTestCaseWithEnvironment(CliBootStrapTestCase): self.username = uuid.uuid4().hex self.project_name = uuid.uuid4().hex self.role_name = uuid.uuid4().hex + self.service_name = uuid.uuid4().hex + self.public_url = uuid.uuid4().hex + self.internal_url = uuid.uuid4().hex + self.admin_url = uuid.uuid4().hex + self.region_id = uuid.uuid4().hex self.default_domain = { 'id': CONF.identity.default_domain_id, 'name': 'Default', @@ -123,6 +152,21 @@ class CliBootStrapTestCaseWithEnvironment(CliBootStrapTestCase): self.useFixture( fixtures.EnvironmentVariable('OS_BOOTSTRAP_ROLE_NAME', newvalue=self.role_name)) + self.useFixture( + fixtures.EnvironmentVariable('OS_BOOTSTRAP_SERVICE_NAME', + newvalue=self.service_name)) + self.useFixture( + fixtures.EnvironmentVariable('OS_BOOTSTRAP_PUBLIC_URL', + newvalue=self.public_url)) + self.useFixture( + fixtures.EnvironmentVariable('OS_BOOTSTRAP_INTERNAL_URL', + newvalue=self.internal_url)) + self.useFixture( + fixtures.EnvironmentVariable('OS_BOOTSTRAP_ADMIN_URL', + newvalue=self.admin_url)) + self.useFixture( + fixtures.EnvironmentVariable('OS_BOOTSTRAP_REGION_ID', + newvalue=self.region_id)) def test_assignment_created_with_user_exists(self): # test assignment can be created if user already exists. @@ -155,6 +199,43 @@ class CliBootStrapTestCaseWithEnvironment(CliBootStrapTestCase): bootstrap.role_manager.create_role(role['id'], role) self._do_test_bootstrap(bootstrap) + def test_assignment_created_with_region_exists(self): + # test assignment can be created if role already exists. + bootstrap = cli.BootStrap() + bootstrap.resource_manager.create_domain(self.default_domain['id'], + self.default_domain) + region = unit.new_region_ref(id=self.region_id) + bootstrap.catalog_manager.create_region(region) + self._do_test_bootstrap(bootstrap) + + def test_endpoints_created_with_service_exists(self): + # test assignment can be created if role already exists. + bootstrap = cli.BootStrap() + bootstrap.resource_manager.create_domain(self.default_domain['id'], + self.default_domain) + service = unit.new_service_ref(name=self.service_name) + bootstrap.catalog_manager.create_service(service['id'], service) + self._do_test_bootstrap(bootstrap) + + def test_endpoints_created_with_endpoint_exists(self): + # test assignment can be created if role already exists. + bootstrap = cli.BootStrap() + bootstrap.resource_manager.create_domain(self.default_domain['id'], + self.default_domain) + service = unit.new_service_ref(name=self.service_name) + bootstrap.catalog_manager.create_service(service['id'], service) + + region = unit.new_region_ref(id=self.region_id) + bootstrap.catalog_manager.create_region(region) + + endpoint = unit.new_endpoint_ref(interface='public', + service_id=service['id'], + url=self.public_url, + region_id=self.region_id) + bootstrap.catalog_manager.create_endpoint(endpoint['id'], endpoint) + + self._do_test_bootstrap(bootstrap) + class CliDomainConfigAllTestCase(unit.SQLDriverOverrides, unit.TestCase):