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:
parent
4077ec823c
commit
96f09de818
@ -7,17 +7,17 @@ export OS_PASSWORD
|
|||||||
OS_AUTH_TYPE=password
|
OS_AUTH_TYPE=password
|
||||||
export OS_AUTH_TYPE
|
export OS_AUTH_TYPE
|
||||||
{{#service_certificate}}
|
{{#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"
|
PYTHONWARNINGS="ignore:Certificate has no, ignore:A true SSLContext object is not available"
|
||||||
export OS_AUTH_URL
|
export OS_AUTH_URL
|
||||||
export PYTHONWARNINGS
|
export PYTHONWARNINGS
|
||||||
{{/service_certificate}}
|
{{/service_certificate}}
|
||||||
{{^service_certificate}}
|
{{^service_certificate}}
|
||||||
OS_AUTH_URL=http://{{local-ip}}:5000/v2.0
|
OS_AUTH_URL=http://{{local-ip}}:5000/
|
||||||
export OS_AUTH_URL
|
export OS_AUTH_URL
|
||||||
{{/service_certificate}}
|
{{/service_certificate}}
|
||||||
OS_USERNAME=admin
|
OS_USERNAME=admin
|
||||||
OS_TENANT_NAME=admin
|
OS_PROJECT_NAME=admin
|
||||||
COMPUTE_API_VERSION=1.1
|
COMPUTE_API_VERSION=1.1
|
||||||
# 1.29 is the latest API version in Ironic Ocata supported by ironicclient
|
# 1.29 is the latest API version in Ironic Ocata supported by ironicclient
|
||||||
IRONIC_API_VERSION=1.29
|
IRONIC_API_VERSION=1.29
|
||||||
@ -25,12 +25,18 @@ OS_BAREMETAL_API_VERSION=$IRONIC_API_VERSION
|
|||||||
OS_NO_CACHE=True
|
OS_NO_CACHE=True
|
||||||
OS_CLOUDNAME=undercloud
|
OS_CLOUDNAME=undercloud
|
||||||
export OS_USERNAME
|
export OS_USERNAME
|
||||||
export OS_TENANT_NAME
|
export OS_PROJECT_NAME
|
||||||
export COMPUTE_API_VERSION
|
export COMPUTE_API_VERSION
|
||||||
export IRONIC_API_VERSION
|
export IRONIC_API_VERSION
|
||||||
export OS_BAREMETAL_API_VERSION
|
export OS_BAREMETAL_API_VERSION
|
||||||
export OS_NO_CACHE
|
export OS_NO_CACHE
|
||||||
export OS_CLOUDNAME
|
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
|
# Add OS_CLOUDNAME to PS1
|
||||||
if [ -z "${CLOUDPROMPT_ENABLED:-}" ]; then
|
if [ -z "${CLOUDPROMPT_ENABLED:-}" ]; then
|
||||||
|
@ -22,7 +22,6 @@ import tempfile
|
|||||||
import fixtures
|
import fixtures
|
||||||
from keystoneauth1 import exceptions as ks_exceptions
|
from keystoneauth1 import exceptions as ks_exceptions
|
||||||
import mock
|
import mock
|
||||||
from mistralclient.api import base as mistralclient_base
|
|
||||||
from novaclient import exceptions
|
from novaclient import exceptions
|
||||||
from oslo_config import fixture as config_fixture
|
from oslo_config import fixture as config_fixture
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
@ -135,10 +134,10 @@ class TestUndercloud(BaseTestCase):
|
|||||||
def test_extract_from_stackrc(self):
|
def test_extract_from_stackrc(self):
|
||||||
with open(os.path.expanduser('~/stackrc'), 'w') as f:
|
with open(os.path.expanduser('~/stackrc'), 'w') as f:
|
||||||
f.write('OS_USERNAME=aturing\n')
|
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',
|
self.assertEqual('aturing',
|
||||||
undercloud._extract_from_stackrc('OS_USERNAME'))
|
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'))
|
undercloud._extract_from_stackrc('OS_AUTH_URL'))
|
||||||
|
|
||||||
@mock.patch('instack_undercloud.undercloud._check_hostname')
|
@mock.patch('instack_undercloud.undercloud._check_hostname')
|
||||||
@ -662,7 +661,7 @@ class TestPostConfig(base.BaseTestCase):
|
|||||||
'http://192.168.24.1:8989/v2',
|
'http://192.168.24.1:8989/v2',
|
||||||
}
|
}
|
||||||
mock_get_auth_values.return_value = ('aturing', '3nigma', 'hut8',
|
mock_get_auth_values.return_value = ('aturing', '3nigma', 'hut8',
|
||||||
'http://bletchley:5000/v2.0')
|
'http://bletchley:5000/')
|
||||||
mock_instance_nova = mock.Mock()
|
mock_instance_nova = mock.Mock()
|
||||||
mock_nova_client.return_value = mock_instance_nova
|
mock_nova_client.return_value = mock_instance_nova
|
||||||
mock_instance_swift = mock.Mock()
|
mock_instance_swift = mock.Mock()
|
||||||
@ -672,7 +671,7 @@ class TestPostConfig(base.BaseTestCase):
|
|||||||
undercloud._post_config(instack_env)
|
undercloud._post_config(instack_env)
|
||||||
mock_nova_client.assert_called_with(
|
mock_nova_client.assert_called_with(
|
||||||
2, 'aturing', '3nigma', project_name='hut8',
|
2, 'aturing', '3nigma', project_name='hut8',
|
||||||
auth_url='http://bletchley:5000/v2.0')
|
auth_url='http://bletchley:5000/')
|
||||||
self.assertTrue(mock_copy_stackrc.called)
|
self.assertTrue(mock_copy_stackrc.called)
|
||||||
mock_configure_ssh_keys.assert_called_with(mock_instance_nova)
|
mock_configure_ssh_keys.assert_called_with(mock_instance_nova)
|
||||||
calls = [mock.call(mock_instance_nova, 'baremetal'),
|
calls = [mock.call(mock_instance_nova, 'baremetal'),
|
||||||
@ -710,7 +709,7 @@ class TestPostConfig(base.BaseTestCase):
|
|||||||
def test_create_config_environment(self):
|
def test_create_config_environment(self):
|
||||||
mock_mistral = mock.Mock()
|
mock_mistral = mock.Mock()
|
||||||
mock_mistral.environments.get.side_effect = (
|
mock_mistral.environments.get.side_effect = (
|
||||||
mistralclient_base.APIException)
|
ks_exceptions.NotFound)
|
||||||
|
|
||||||
env = {
|
env = {
|
||||||
"UNDERCLOUD_CEILOMETER_SNMPD_PASSWORD": "snmpd-pass"
|
"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):
|
def _mock_ksclient_roles(self, mock_auth_values, mock_ksdiscover, roles):
|
||||||
mock_auth_values.return_value = ('user', 'password',
|
mock_auth_values.return_value = ('user', 'password',
|
||||||
'tenant', 'http://test:123')
|
'project', 'http://test:123')
|
||||||
mock_discover = mock.Mock()
|
mock_discover = mock.Mock()
|
||||||
mock_ksdiscover.return_value = mock_discover
|
mock_ksdiscover.return_value = mock_discover
|
||||||
mock_client = mock.Mock()
|
mock_client = mock.Mock()
|
||||||
@ -784,12 +783,14 @@ class TestPostConfig(base.BaseTestCase):
|
|||||||
mock_client.roles = mock_roles
|
mock_client.roles = mock_roles
|
||||||
mock_discover.create_client.return_value = mock_client
|
mock_discover.create_client.return_value = mock_client
|
||||||
|
|
||||||
mock_tenant_list = [mock.Mock(), mock.Mock()]
|
mock_client.version = 'v3'
|
||||||
mock_tenant_list[0].name = 'admin'
|
|
||||||
mock_tenant_list[0].id = 'admin-id'
|
mock_project_list = [mock.Mock(), mock.Mock()]
|
||||||
mock_tenant_list[1].name = 'service'
|
mock_project_list[0].name = 'admin'
|
||||||
mock_tenant_list[1].id = 'service-id'
|
mock_project_list[0].id = 'admin-id'
|
||||||
mock_client.tenants.list.return_value = mock_tenant_list
|
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 = [mock.Mock(), mock.Mock()]
|
||||||
mock_user_list[0].name = 'admin'
|
mock_user_list[0].name = 'admin'
|
||||||
@ -807,7 +808,7 @@ class TestPostConfig(base.BaseTestCase):
|
|||||||
mock_ksdiscover,
|
mock_ksdiscover,
|
||||||
['admin'])
|
['admin'])
|
||||||
undercloud._member_role_exists()
|
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('keystoneclient.discover.Discover')
|
||||||
@mock.patch('instack_undercloud.undercloud._get_auth_values')
|
@mock.patch('instack_undercloud.undercloud._get_auth_values')
|
||||||
@ -821,8 +822,8 @@ class TestPostConfig(base.BaseTestCase):
|
|||||||
undercloud._member_role_exists()
|
undercloud._member_role_exists()
|
||||||
mock_user = mock_client.users.list.return_value[0]
|
mock_user = mock_client.users.list.return_value[0]
|
||||||
mock_role = mock_client.roles.list.return_value[1]
|
mock_role = mock_client.roles.list.return_value[1]
|
||||||
mock_client.roles.add_user_role.assert_called_once_with(
|
mock_client.roles.grant.assert_called_once_with(
|
||||||
mock_user, mock_role, 'admin-id')
|
mock_role, user=mock_user, project='admin-id')
|
||||||
|
|
||||||
@mock.patch('keystoneclient.discover.Discover')
|
@mock.patch('keystoneclient.discover.Discover')
|
||||||
@mock.patch('instack_undercloud.undercloud._get_auth_values')
|
@mock.patch('instack_undercloud.undercloud._get_auth_values')
|
||||||
@ -834,12 +835,12 @@ class TestPostConfig(base.BaseTestCase):
|
|||||||
mock_ksdiscover,
|
mock_ksdiscover,
|
||||||
['admin', '_member_'])
|
['admin', '_member_'])
|
||||||
fake_exception = ks_exceptions.http.Conflict('test')
|
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()
|
undercloud._member_role_exists()
|
||||||
mock_user = mock_client.users.list.return_value[0]
|
mock_user = mock_client.users.list.return_value[0]
|
||||||
mock_role = mock_client.roles.list.return_value[1]
|
mock_role = mock_client.roles.list.return_value[1]
|
||||||
mock_client.roles.add_user_role.assert_called_once_with(
|
mock_client.roles.grant.assert_called_once_with(
|
||||||
mock_user, mock_role, 'admin-id')
|
mock_role, user=mock_user, project='admin-id')
|
||||||
|
|
||||||
def _create_flavor_mocks(self):
|
def _create_flavor_mocks(self):
|
||||||
mock_nova = mock.Mock()
|
mock_nova = mock.Mock()
|
||||||
|
@ -31,12 +31,11 @@ import time
|
|||||||
import uuid
|
import uuid
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from keystoneauth1 import exceptions as ks_exceptions
|
|
||||||
from keystoneauth1 import session
|
from keystoneauth1 import session
|
||||||
from keystoneclient import auth
|
from keystoneauth1 import exceptions as ks_exceptions
|
||||||
from keystoneclient import discover
|
from keystoneclient import discover
|
||||||
|
import keystoneauth1.identity.generic as ks_auth
|
||||||
from mistralclient.api import client as mistralclient
|
from mistralclient.api import client as mistralclient
|
||||||
from mistralclient.api import base as mistralclient_base
|
|
||||||
import novaclient as nc
|
import novaclient as nc
|
||||||
from novaclient import client as novaclient
|
from novaclient import client as novaclient
|
||||||
from novaclient import exceptions
|
from novaclient import exceptions
|
||||||
@ -941,20 +940,16 @@ def _member_role_exists():
|
|||||||
# This is a workaround for puppet removing the deprecated _member_
|
# This is a workaround for puppet removing the deprecated _member_
|
||||||
# role on upgrade - if it exists we must restore role assignments
|
# role on upgrade - if it exists we must restore role assignments
|
||||||
# or trusts stored in the undercloud heat will break
|
# or trusts stored in the undercloud heat will break
|
||||||
user, password, tenant, auth_url = _get_auth_values()
|
user, password, project, 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')
|
|
||||||
auth_kwargs = {
|
auth_kwargs = {
|
||||||
'auth_url': auth_url,
|
'auth_url': auth_url,
|
||||||
'username': user,
|
'username': user,
|
||||||
'password': password,
|
'password': password,
|
||||||
'project_name': tenant}
|
'project_name': project,
|
||||||
if 'v2.0' not in auth_url:
|
'project_domain_name': 'Default',
|
||||||
auth_kwargs.update({
|
'user_domain_name': 'Default',
|
||||||
'project_domain_name': 'default',
|
}
|
||||||
'user_domain_name': 'default'})
|
auth_plugin = ks_auth.Password(**auth_kwargs)
|
||||||
auth_plugin = auth_plugin_class(**auth_kwargs)
|
|
||||||
sess = session.Session(auth=auth_plugin)
|
sess = session.Session(auth=auth_plugin)
|
||||||
disc = discover.Discover(session=sess)
|
disc = discover.Discover(session=sess)
|
||||||
c = disc.create_client()
|
c = disc.create_client()
|
||||||
@ -963,10 +958,24 @@ def _member_role_exists():
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
# Do nothing if there is no _member_ role
|
# Do nothing if there is no _member_ role
|
||||||
return
|
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]
|
admin_user = [u for u in c.users.list() if u.name == 'admin'][0]
|
||||||
|
if c.version == 'v2.0':
|
||||||
try:
|
try:
|
||||||
c.roles.add_user_role(admin_user, member_role, admin_tenant.id)
|
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')
|
LOG.info('Added _member_ role to admin user')
|
||||||
except ks_exceptions.http.Conflict:
|
except ks_exceptions.http.Conflict:
|
||||||
# They already had the role
|
# They already had the role
|
||||||
@ -1243,14 +1252,14 @@ def _ensure_user_identity(id_path):
|
|||||||
def _get_auth_values():
|
def _get_auth_values():
|
||||||
"""Get auth values from stackrc
|
"""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.
|
in that order as a tuple.
|
||||||
"""
|
"""
|
||||||
user = _extract_from_stackrc('OS_USERNAME')
|
user = _extract_from_stackrc('OS_USERNAME')
|
||||||
password = _run_command(['sudo', 'hiera', 'admin_password']).rstrip()
|
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')
|
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):
|
def _configure_ssh_keys(nova):
|
||||||
@ -1325,7 +1334,7 @@ def _create_mistral_config_environment(instack_env, mistral):
|
|||||||
env_name = "tripleo.undercloud-config"
|
env_name = "tripleo.undercloud-config"
|
||||||
try:
|
try:
|
||||||
mistral.environments.get(env_name)
|
mistral.environments.get(env_name)
|
||||||
except mistralclient_base.APIException:
|
except ks_exceptions.NotFound:
|
||||||
mistral.environments.create(
|
mistral.environments.create(
|
||||||
name=env_name,
|
name=env_name,
|
||||||
variables=json.dumps({
|
variables=json.dumps({
|
||||||
@ -1411,13 +1420,23 @@ def _post_config_mistral(instack_env, mistral, swift):
|
|||||||
|
|
||||||
def _post_config(instack_env):
|
def _post_config(instack_env):
|
||||||
_copy_stackrc()
|
_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
|
# TODO(andreykurilin): remove this check with support of novaclient 6.0.0
|
||||||
if nc.__version__[0] == "6":
|
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:
|
else:
|
||||||
nova = novaclient.Client(2, user, password, auth_url=auth_url,
|
nova = novaclient.Client(2, user, password, auth_url=auth_url,
|
||||||
project_name=tenant)
|
project_name=project)
|
||||||
|
|
||||||
_configure_ssh_keys(nova)
|
_configure_ssh_keys(nova)
|
||||||
_delete_default_flavors(nova)
|
_delete_default_flavors(nova)
|
||||||
@ -1432,18 +1451,11 @@ def _post_config(instack_env):
|
|||||||
mistral_url = instack_env['UNDERCLOUD_ENDPOINT_MISTRAL_PUBLIC']
|
mistral_url = instack_env['UNDERCLOUD_ENDPOINT_MISTRAL_PUBLIC']
|
||||||
mistral = mistralclient.client(
|
mistral = mistralclient.client(
|
||||||
mistral_url=mistral_url,
|
mistral_url=mistral_url,
|
||||||
username=user,
|
session=sess)
|
||||||
api_key=password,
|
|
||||||
project_name=tenant,
|
|
||||||
auth_url=auth_url)
|
|
||||||
swift = swiftclient.Connection(
|
swift = swiftclient.Connection(
|
||||||
authurl=auth_url,
|
authurl=auth_url,
|
||||||
user=user,
|
session=sess
|
||||||
key=password,
|
|
||||||
tenant_name=tenant,
|
|
||||||
auth_version='2.0'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_post_config_mistral(instack_env, mistral, swift)
|
_post_config_mistral(instack_env, mistral, swift)
|
||||||
_member_role_exists()
|
_member_role_exists()
|
||||||
|
|
||||||
|
6
releasenotes/notes/stackrc-v3-1e4513172af13806.yaml
Normal file
6
releasenotes/notes/stackrc-v3-1e4513172af13806.yaml
Normal 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.
|
Loading…
Reference in New Issue
Block a user