Switch stackrc and undercloud.py to use Keystone v3

- Update undercloud stackrc to use Keystone v3 API.
  It updates OS_AUTH_URL to use a versionless endpoint, change OS_TENANT_NAME
  to OS_PROJECT_NAME, force OS_IDENTITY_API_VERSION to 3 and
  add OS_PROJECT_DOMAIN_NAME pointing to default domain.

- In undercloud.py, rename tenant to project which is the right word
  in Keystone v3 vocabulary.

- Support the _member_ role addition to the admin user using v3

Co-Authored-By: Juan Antonio Osorio Robles <jaosorior@redhat.com>
Change-Id: I4fe6d9767a104af1cfef83b27869df7210e65b83
Partial-implement: blueprint keystone-v3
This commit is contained in:
Emilien Macchi 2017-03-16 17:35:28 -04:00 committed by Juan Antonio Osorio Robles
parent 4077ec823c
commit 96f09de818
4 changed files with 84 additions and 59 deletions

View File

@ -7,17 +7,17 @@ export OS_PASSWORD
OS_AUTH_TYPE=password
export OS_AUTH_TYPE
{{#service_certificate}}
OS_AUTH_URL=https://{{public_host}}:13000/v2.0
OS_AUTH_URL=https://{{public_host}}:13000/
PYTHONWARNINGS="ignore:Certificate has no, ignore:A true SSLContext object is not available"
export OS_AUTH_URL
export PYTHONWARNINGS
{{/service_certificate}}
{{^service_certificate}}
OS_AUTH_URL=http://{{local-ip}}:5000/v2.0
OS_AUTH_URL=http://{{local-ip}}:5000/
export OS_AUTH_URL
{{/service_certificate}}
OS_USERNAME=admin
OS_TENANT_NAME=admin
OS_PROJECT_NAME=admin
COMPUTE_API_VERSION=1.1
# 1.29 is the latest API version in Ironic Ocata supported by ironicclient
IRONIC_API_VERSION=1.29
@ -25,12 +25,18 @@ OS_BAREMETAL_API_VERSION=$IRONIC_API_VERSION
OS_NO_CACHE=True
OS_CLOUDNAME=undercloud
export OS_USERNAME
export OS_TENANT_NAME
export OS_PROJECT_NAME
export COMPUTE_API_VERSION
export IRONIC_API_VERSION
export OS_BAREMETAL_API_VERSION
export OS_NO_CACHE
export OS_CLOUDNAME
OS_IDENTITY_API_VERSION='3'
export OS_IDENTITY_API_VERSION
OS_PROJECT_DOMAIN_NAME='Default'
export OS_PROJECT_DOMAIN_NAME
OS_USER_DOMAIN_NAME='Default'
export OS_USER_DOMAIN_NAME
# Add OS_CLOUDNAME to PS1
if [ -z "${CLOUDPROMPT_ENABLED:-}" ]; then

View File

@ -22,7 +22,6 @@ import tempfile
import fixtures
from keystoneauth1 import exceptions as ks_exceptions
import mock
from mistralclient.api import base as mistralclient_base
from novaclient import exceptions
from oslo_config import fixture as config_fixture
from oslotest import base
@ -135,10 +134,10 @@ class TestUndercloud(BaseTestCase):
def test_extract_from_stackrc(self):
with open(os.path.expanduser('~/stackrc'), 'w') as f:
f.write('OS_USERNAME=aturing\n')
f.write('OS_AUTH_URL=http://bletchley:5000/v2.0\n')
f.write('OS_AUTH_URL=http://bletchley:5000/\n')
self.assertEqual('aturing',
undercloud._extract_from_stackrc('OS_USERNAME'))
self.assertEqual('http://bletchley:5000/v2.0',
self.assertEqual('http://bletchley:5000/',
undercloud._extract_from_stackrc('OS_AUTH_URL'))
@mock.patch('instack_undercloud.undercloud._check_hostname')
@ -662,7 +661,7 @@ class TestPostConfig(base.BaseTestCase):
'http://192.168.24.1:8989/v2',
}
mock_get_auth_values.return_value = ('aturing', '3nigma', 'hut8',
'http://bletchley:5000/v2.0')
'http://bletchley:5000/')
mock_instance_nova = mock.Mock()
mock_nova_client.return_value = mock_instance_nova
mock_instance_swift = mock.Mock()
@ -672,7 +671,7 @@ class TestPostConfig(base.BaseTestCase):
undercloud._post_config(instack_env)
mock_nova_client.assert_called_with(
2, 'aturing', '3nigma', project_name='hut8',
auth_url='http://bletchley:5000/v2.0')
auth_url='http://bletchley:5000/')
self.assertTrue(mock_copy_stackrc.called)
mock_configure_ssh_keys.assert_called_with(mock_instance_nova)
calls = [mock.call(mock_instance_nova, 'baremetal'),
@ -710,7 +709,7 @@ class TestPostConfig(base.BaseTestCase):
def test_create_config_environment(self):
mock_mistral = mock.Mock()
mock_mistral.environments.get.side_effect = (
mistralclient_base.APIException)
ks_exceptions.NotFound)
env = {
"UNDERCLOUD_CEILOMETER_SNMPD_PASSWORD": "snmpd-pass"
@ -770,7 +769,7 @@ class TestPostConfig(base.BaseTestCase):
def _mock_ksclient_roles(self, mock_auth_values, mock_ksdiscover, roles):
mock_auth_values.return_value = ('user', 'password',
'tenant', 'http://test:123')
'project', 'http://test:123')
mock_discover = mock.Mock()
mock_ksdiscover.return_value = mock_discover
mock_client = mock.Mock()
@ -784,12 +783,14 @@ class TestPostConfig(base.BaseTestCase):
mock_client.roles = mock_roles
mock_discover.create_client.return_value = mock_client
mock_tenant_list = [mock.Mock(), mock.Mock()]
mock_tenant_list[0].name = 'admin'
mock_tenant_list[0].id = 'admin-id'
mock_tenant_list[1].name = 'service'
mock_tenant_list[1].id = 'service-id'
mock_client.tenants.list.return_value = mock_tenant_list
mock_client.version = 'v3'
mock_project_list = [mock.Mock(), mock.Mock()]
mock_project_list[0].name = 'admin'
mock_project_list[0].id = 'admin-id'
mock_project_list[1].name = 'service'
mock_project_list[1].id = 'service-id'
mock_client.projects.list.return_value = mock_project_list
mock_user_list = [mock.Mock(), mock.Mock()]
mock_user_list[0].name = 'admin'
@ -807,7 +808,7 @@ class TestPostConfig(base.BaseTestCase):
mock_ksdiscover,
['admin'])
undercloud._member_role_exists()
self.assertFalse(mock_client.tenants.list.called)
self.assertFalse(mock_client.projects.list.called)
@mock.patch('keystoneclient.discover.Discover')
@mock.patch('instack_undercloud.undercloud._get_auth_values')
@ -821,8 +822,8 @@ class TestPostConfig(base.BaseTestCase):
undercloud._member_role_exists()
mock_user = mock_client.users.list.return_value[0]
mock_role = mock_client.roles.list.return_value[1]
mock_client.roles.add_user_role.assert_called_once_with(
mock_user, mock_role, 'admin-id')
mock_client.roles.grant.assert_called_once_with(
mock_role, user=mock_user, project='admin-id')
@mock.patch('keystoneclient.discover.Discover')
@mock.patch('instack_undercloud.undercloud._get_auth_values')
@ -834,12 +835,12 @@ class TestPostConfig(base.BaseTestCase):
mock_ksdiscover,
['admin', '_member_'])
fake_exception = ks_exceptions.http.Conflict('test')
mock_client.roles.add_user_role.side_effect = fake_exception
mock_client.roles.grant.side_effect = fake_exception
undercloud._member_role_exists()
mock_user = mock_client.users.list.return_value[0]
mock_role = mock_client.roles.list.return_value[1]
mock_client.roles.add_user_role.assert_called_once_with(
mock_user, mock_role, 'admin-id')
mock_client.roles.grant.assert_called_once_with(
mock_role, user=mock_user, project='admin-id')
def _create_flavor_mocks(self):
mock_nova = mock.Mock()

View File

@ -31,12 +31,11 @@ import time
import uuid
import yaml
from keystoneauth1 import exceptions as ks_exceptions
from keystoneauth1 import session
from keystoneclient import auth
from keystoneauth1 import exceptions as ks_exceptions
from keystoneclient import discover
import keystoneauth1.identity.generic as ks_auth
from mistralclient.api import client as mistralclient
from mistralclient.api import base as mistralclient_base
import novaclient as nc
from novaclient import client as novaclient
from novaclient import exceptions
@ -941,20 +940,16 @@ def _member_role_exists():
# This is a workaround for puppet removing the deprecated _member_
# role on upgrade - if it exists we must restore role assignments
# or trusts stored in the undercloud heat will break
user, password, tenant, auth_url = _get_auth_values()
# Note this is made somewhat verbose due to trying to handle
# any format auth_url (versionless, v2,0/v3 suffix)
auth_plugin_class = auth.get_plugin_class('password')
user, password, project, auth_url = _get_auth_values()
auth_kwargs = {
'auth_url': auth_url,
'username': user,
'password': password,
'project_name': tenant}
if 'v2.0' not in auth_url:
auth_kwargs.update({
'project_domain_name': 'default',
'user_domain_name': 'default'})
auth_plugin = auth_plugin_class(**auth_kwargs)
'project_name': project,
'project_domain_name': 'Default',
'user_domain_name': 'Default',
}
auth_plugin = ks_auth.Password(**auth_kwargs)
sess = session.Session(auth=auth_plugin)
disc = discover.Discover(session=sess)
c = disc.create_client()
@ -963,14 +958,28 @@ def _member_role_exists():
except IndexError:
# Do nothing if there is no _member_ role
return
admin_tenant = [t for t in c.tenants.list() if t.name == 'admin'][0]
if c.version == 'v2.0':
client_projects = c.tenants
else:
client_projects = c.projects
admin_project = [t for t in client_projects.list() if t.name == 'admin'][0]
admin_user = [u for u in c.users.list() if u.name == 'admin'][0]
try:
c.roles.add_user_role(admin_user, member_role, admin_tenant.id)
LOG.info('Added _member_ role to admin user')
except ks_exceptions.http.Conflict:
# They already had the role
pass
if c.version == 'v2.0':
try:
c.roles.add_user_role(admin_user, member_role, admin_project.id)
LOG.info('Added _member_ role to admin user')
except ks_exceptions.http.Conflict:
# They already had the role
pass
else:
try:
c.roles.grant(member_role,
user=admin_user,
project=admin_project.id)
LOG.info('Added _member_ role to admin user')
except ks_exceptions.http.Conflict:
# They already had the role
pass
class InstackEnvironment(dict):
@ -1243,14 +1252,14 @@ def _ensure_user_identity(id_path):
def _get_auth_values():
"""Get auth values from stackrc
Returns the user, password, tenant and auth_url as read from stackrc,
Returns the user, password, project and auth_url as read from stackrc,
in that order as a tuple.
"""
user = _extract_from_stackrc('OS_USERNAME')
password = _run_command(['sudo', 'hiera', 'admin_password']).rstrip()
tenant = _extract_from_stackrc('OS_TENANT_NAME')
project = _extract_from_stackrc('OS_PROJECT_NAME')
auth_url = _extract_from_stackrc('OS_AUTH_URL')
return user, password, tenant, auth_url
return user, password, project, auth_url
def _configure_ssh_keys(nova):
@ -1325,7 +1334,7 @@ def _create_mistral_config_environment(instack_env, mistral):
env_name = "tripleo.undercloud-config"
try:
mistral.environments.get(env_name)
except mistralclient_base.APIException:
except ks_exceptions.NotFound:
mistral.environments.create(
name=env_name,
variables=json.dumps({
@ -1411,13 +1420,23 @@ def _post_config_mistral(instack_env, mistral, swift):
def _post_config(instack_env):
_copy_stackrc()
user, password, tenant, auth_url = _get_auth_values()
user, password, project, auth_url = _get_auth_values()
auth_kwargs = {
'auth_url': auth_url,
'username': user,
'password': password,
'project_name': project,
'project_domain_name': 'Default',
'user_domain_name': 'Default',
}
auth_plugin = ks_auth.Password(**auth_kwargs)
sess = session.Session(auth=auth_plugin)
# TODO(andreykurilin): remove this check with support of novaclient 6.0.0
if nc.__version__[0] == "6":
nova = novaclient.Client(2, user, password, tenant, auth_url=auth_url)
nova = novaclient.Client(2, user, password, project, auth_url=auth_url)
else:
nova = novaclient.Client(2, user, password, auth_url=auth_url,
project_name=tenant)
project_name=project)
_configure_ssh_keys(nova)
_delete_default_flavors(nova)
@ -1432,18 +1451,11 @@ def _post_config(instack_env):
mistral_url = instack_env['UNDERCLOUD_ENDPOINT_MISTRAL_PUBLIC']
mistral = mistralclient.client(
mistral_url=mistral_url,
username=user,
api_key=password,
project_name=tenant,
auth_url=auth_url)
session=sess)
swift = swiftclient.Connection(
authurl=auth_url,
user=user,
key=password,
tenant_name=tenant,
auth_version='2.0'
session=sess
)
_post_config_mistral(instack_env, mistral, swift)
_member_role_exists()

View File

@ -0,0 +1,6 @@
---
features:
- Update undercloud stackrc to use Keystone v3 API.
It updates OS_AUTH_URL to use a versionless endpoint, change OS_TENANT_NAME
to OS_PROJECT_NAME, force OS_IDENTITY_API_VERSION to 3 and
add OS_PROJECT_DOMAIN_NAME pointing to default domain.