adopt ec2 api to work with keystone v3 api

Change-Id: Idbafeff0aff9a32852ef0dbeaae5b341a7c06b61
This commit is contained in:
Andrey Pavlov 2015-08-10 10:20:53 +03:00
parent e630f2159f
commit 0167580e6d
8 changed files with 101 additions and 34 deletions

View File

@ -213,7 +213,6 @@ Legacy OpenStack release notice
EC2 API supports Havana, Icehouse, Juno with additional limitations:
Instance related:
- rootDeviceName Instance property
- kernelId Instance property
@ -237,7 +236,8 @@ EC2 API supports Nova client (>=2.16.0) with no microversion support.
Additional limitations are the same, except network interfaces'
deleteOnTermination.
Preferred way to run EC2 API in these releases is to run it in virtual environment:
Preferred way to run EC2 API in older releases is to run it in virtual environment:
- create virtual environment by running command 'python tools/install_venv.py'
- run install inside venv 'tools/with_venv.sh ./install.sh'
- and then you need to run EC2 API services: 'ec2-api', 'ec2-api-metadata'

View File

@ -228,11 +228,21 @@ class EC2KeystoneAuth(wsgi.Middleware):
result = response.json()
try:
token_id = result['access']['token']['id']
user_id = result['access']['user']['id']
project_id = result['access']['token']['tenant']['id']
user_name = result['access']['user'].get('name')
project_name = result['access']['token']['tenant'].get('name')
if 'token' in result:
# NOTE(andrey-mp): response from keystone v3
token_id = response.headers['x-subject-token']
user_id = result['token']['user']['id']
project_id = result['token']['project']['id']
user_name = result['token']['user'].get('name')
project_name = result['token']['project'].get('name')
catalog = result['token']['catalog']
else:
token_id = result['access']['token']['id']
user_id = result['access']['user']['id']
project_id = result['access']['token']['tenant']['id']
user_name = result['access']['user'].get('name')
project_name = result['access']['token']['tenant'].get('name')
catalog = result['access']['serviceCatalog']
except (AttributeError, KeyError):
LOG.exception(_("Keystone failure"))
msg = _("Failure communicating with keystone")
@ -244,7 +254,6 @@ class EC2KeystoneAuth(wsgi.Middleware):
remote_address = req.headers.get('X-Forwarded-For',
remote_address)
catalog = result['access']['serviceCatalog']
ctxt = context.RequestContext(user_id, project_id,
request_id=request_id,
user_name=user_name,

View File

@ -13,7 +13,6 @@
# limitations under the License.
from keystoneclient.v2_0 import client as kc
from novaclient import client as novaclient
from novaclient import exceptions as nova_exception
from oslo_config import cfg
@ -135,13 +134,13 @@ def cinder(context):
def keystone(context):
_keystone = kc.Client(
keystone_client_class = ec2_context.get_keystone_client_class()
return keystone_client_class(
token=context.auth_token,
project_id=context.project_id,
tenant_id=context.project_id,
auth_url=CONF.keystone_url)
return _keystone
def nova_cert(context):
_cert_api = _rpcapi_CertAPI(context)
@ -162,6 +161,9 @@ def _url_for(context, **kwargs):
for endpoint in service['endpoints']:
if 'publicURL' in endpoint:
return endpoint['publicURL']
elif endpoint.get('interface') == 'public':
# NOTE(andrey-mp): keystone v3
return endpoint['url']
else:
return None

View File

@ -16,7 +16,9 @@
import uuid
from keystoneclient.v2_0 import client as keystone_client
from keystoneclient import client as keystone_client
from keystoneclient.v2_0 import client as keystone_client_v2
from keystoneclient.v3 import client as keystone_client_v3
from oslo_config import cfg
from oslo_context import context
from oslo_log import log as logging
@ -35,6 +37,8 @@ ec2_opts = [
secret=True),
cfg.StrOpt('admin_tenant_name',
help=_("Admin tenant name")),
# TODO(andrey-mp): keystone v3 allows to pass domain_name
# or domain_id to auth. This code should support this feature.
]
CONF = cfg.CONF
@ -143,15 +147,33 @@ def is_user_context(context):
return True
_keystone_client_class = None
def get_keystone_client_class():
global _keystone_client_class
if _keystone_client_class is None:
keystone = keystone_client.Client(auth_url=CONF.keystone_url)
if isinstance(keystone, keystone_client_v2.Client):
_keystone_client_class = keystone_client_v2.Client
elif isinstance(keystone, keystone_client_v3.Client):
_keystone_client_class = keystone_client_v3.Client
else:
raise exception.EC2KeystoneDiscoverFailure()
return _keystone_client_class
def get_os_admin_context():
"""Create a context to interact with OpenStack as an administrator."""
current_context = context.get_current()
if (current_context and current_context.is_os_admin):
return current_context
# TODO(ft): make an authentification token reusable
keystone = keystone_client.Client(
keystone_client_class = get_keystone_client_class()
keystone = keystone_client_class(
username=CONF.admin_user,
password=CONF.admin_password,
project_name=CONF.admin_tenant_name,
tenant_name=CONF.admin_tenant_name,
auth_url=CONF.keystone_url,
)

View File

@ -96,6 +96,10 @@ class EC2APIPasteAppNotFound(EC2APIException):
msg_fmt = _("Could not load paste app '%(name)s' from %(path)s")
class EC2KeystoneDiscoverFailure(EC2APIException):
msg_fmt = _("Could not discover keystone versions.")
# Internal ec2api metadata exceptions
class EC2MetadataException(EC2APIException):

View File

@ -123,13 +123,14 @@ class ClientsTestCase(test_base.BaseTestCase):
self.assertEqual('fake_token', res.client.auth_token)
self.assertEqual('cinder_url', res.client.management_url)
@mock.patch('keystoneclient.v2_0.client.Client')
def test_keystone(self, keystone):
@mock.patch('ec2api.context.get_keystone_client_class',
return_value=mock.Mock(return_value=mock.Mock()))
def test_keystone(self, keystone_client_class):
context = mock.NonCallableMock(
auth_token='fake_token',
project_id='fake_project')
res = clients.keystone(context)
self.assertEqual(keystone.return_value, res)
keystone.assert_called_with(
self.assertEqual(keystone_client_class.return_value.return_value, res)
keystone_client_class.return_value.assert_called_with(
auth_url='keystone_url', token='fake_token',
tenant_id='fake_project')
tenant_id='fake_project', project_id='fake_project')

View File

@ -12,12 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from keystoneclient.v2_0 import client as keystone_client_v2
from keystoneclient.v3 import client as keystone_client_v3
import mock
from oslo_config import cfg
from oslo_config import fixture as config_fixture
from oslotest import base as test_base
from ec2api import context as ec2_context
from ec2api import exception
cfg.CONF.import_opt('keystone_url', 'ec2api.api')
@ -35,10 +38,12 @@ class ContextTestCase(test_base.BaseTestCase):
def test_get_os_admin_context(self, keystone):
service_catalog = mock.Mock()
service_catalog.get_data.return_value = 'fake_service_catalog'
keystone.return_value = mock.Mock(auth_user_id='fake_user_id',
auth_tenant_id='fake_project_id',
auth_token='fake_token',
service_catalog=service_catalog)
ec2_context._keystone_client_class = mock.Mock(
return_value=mock.Mock(
auth_user_id='fake_user_id',
auth_tenant_id='fake_project_id',
auth_token='fake_token',
service_catalog=service_catalog))
context = ec2_context.get_os_admin_context()
self.assertEqual('fake_user_id', context.user_id)
self.assertEqual('fake_project_id', context.project_id)
@ -46,13 +51,35 @@ class ContextTestCase(test_base.BaseTestCase):
self.assertEqual('fake_service_catalog', context.service_catalog)
self.assertTrue(context.is_os_admin)
conf = cfg.CONF
keystone.assert_called_once_with(
username=conf.admin_user,
password=conf.admin_password,
tenant_name=conf.admin_tenant_name,
auth_url=conf.keystone_url)
ec2_context._keystone_client_class.assert_called_once_with(
username=conf.admin_user,
password=conf.admin_password,
tenant_name=conf.admin_tenant_name,
project_name=conf.admin_tenant_name,
auth_url=conf.keystone_url)
service_catalog.get_data.assert_called_once_with()
keystone.reset_mock()
self.assertEqual(context, ec2_context.get_os_admin_context())
self.assertFalse(keystone.called)
@mock.patch('keystoneclient.client.Client')
def test_get_keystone_client_class(self, client):
client.return_value = mock.MagicMock(spec=keystone_client_v2.Client)
ec2_context._keystone_client_class = None
client_class = ec2_context.get_keystone_client_class()
client.assert_called_once_with(auth_url='http://localhost:5000/v2.0')
self.assertEqual(keystone_client_v2.Client, client_class)
client.reset_mock()
client.return_value = mock.MagicMock(spec=keystone_client_v3.Client)
ec2_context._keystone_client_class = None
client_class = ec2_context.get_keystone_client_class()
client.assert_called_once_with(auth_url='http://localhost:5000/v2.0')
self.assertEqual(keystone_client_v3.Client, client_class)
client.reset_mock()
client.return_value = mock.MagicMock()
ec2_context._keystone_client_class = None
self.assertRaises(exception.EC2KeystoneDiscoverFailure,
ec2_context.get_keystone_client_class)

View File

@ -333,17 +333,19 @@ class ProxyTestCase(test_base.BaseTestCase):
self.handler._unpack_request_attributes(req)
self.assertEqual(1, constant_time_compare.call_count)
@mock.patch('keystoneclient.v2_0.client.Client')
@mock.patch('ec2api.context.get_keystone_client_class')
@mock.patch('novaclient.client.Client')
@mock.patch('ec2api.db.api.IMPL')
@mock.patch('ec2api.metadata.api.instance_api')
def test_get_metadata(self, instance_api, db_api, nova, keystone):
def test_get_metadata(self, instance_api, db_api, nova,
keystone_client_class):
service_catalog = mock.MagicMock()
service_catalog.get_data.return_value = []
keystone.return_value = mock.Mock(auth_user_id='fake_user_id',
auth_tenant_id='fake_project_id',
auth_token='fake_token',
service_catalog=service_catalog)
keystone_client_class.return_value.return_value = mock.Mock(
auth_user_id='fake_user_id',
auth_tenant_id='fake_project_id',
auth_token='fake_token',
service_catalog=service_catalog)
nova.return_value.fixed_ips.get.return_value = (
mock.Mock(hostname='fake_name'))
nova.return_value.servers.list.return_value = [