From 5480e9b8509e33abf9b3c10eea01b1e80a5225e1 Mon Sep 17 00:00:00 2001 From: Feodor Tersin Date: Mon, 9 Mar 2015 11:33:31 +0300 Subject: [PATCH] Add get_os_admin_context function in context module A preparation step to get rid of Nova DB usage. Move a function of OS admin context initialization to context module to use it in all of API. We're going to use this function to initialize Nova client to get instance object with an admin account (which is the only option to get neccessary extended instance attributes). Change-Id: I195d899ed245ea3489c7e97b56ee6aaff9a08be2 --- devstack/plugin.sh | 6 ++-- ec2api/api/instance.py | 2 +- ec2api/context.py | 39 ++++++++++++++++++++-- ec2api/metadata/__init__.py | 29 ++-------------- ec2api/tests/unit/test_context.py | 53 ++++++++++++++++++++++++++++++ ec2api/tests/unit/test_metadata.py | 31 ++--------------- install.sh | 6 ++-- 7 files changed, 101 insertions(+), 65 deletions(-) create mode 100644 ec2api/tests/unit/test_context.py diff --git a/devstack/plugin.sh b/devstack/plugin.sh index ccf24df9..f1cb0bca 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -176,9 +176,9 @@ function configure_ec2api { iniset $EC2API_CONF_FILE keystone_authtoken admin_password $SERVICE_PASSWORD iniset $EC2API_CONF_FILE keystone_authtoken signing_dir $EC2API_KEYSTONE_SIGNING_DIR - iniset $EC2API_CONF_FILE metadata admin_tenant_name $SERVICE_TENANT_NAME - iniset $EC2API_CONF_FILE metadata admin_user $EC2API_ADMIN_USER - iniset $EC2API_CONF_FILE metadata admin_password $SERVICE_PASSWORD + iniset $EC2API_CONF_FILE DEFAULT admin_tenant_name $SERVICE_TENANT_NAME + iniset $EC2API_CONF_FILE DEFAULT admin_user $EC2API_ADMIN_USER + iniset $EC2API_CONF_FILE DEFAULT admin_password $SERVICE_PASSWORD iniset $EC2API_CONF_FILE DEFAULT keystone_url "http://${KEYSTONE_AUTH_HOST}:35357/v2.0" iniset $EC2API_CONF_FILE DEFAULT region_list "$REGION_NAME" diff --git a/ec2api/api/instance.py b/ec2api/api/instance.py index e917caa5..21ba45ec 100644 --- a/ec2api/api/instance.py +++ b/ec2api/api/instance.py @@ -212,7 +212,7 @@ class InstanceDescriber(common.TaggableItemsDescriber): # NOTE(ft): these filters are needed for metadata server # which calls describe_instances with an admin account # (but project_id is substituted to an instance's one). - search_opts={'all_tenants': self.context.cross_tenants, + search_opts={'all_tenants': self.context.is_os_admin, 'project_id': self.context.project_id}) def auto_update_db(self, instance, os_instance): diff --git a/ec2api/context.py b/ec2api/context.py index 8da3e6d3..1ef75600 100644 --- a/ec2api/context.py +++ b/ec2api/context.py @@ -16,6 +16,8 @@ import uuid +from keystoneclient.v2_0 import client as keystone_client +from oslo_config import cfg from oslo_log import log as logging from oslo_utils import timeutils import six @@ -25,6 +27,19 @@ from ec2api.i18n import _ from ec2api.openstack.common import local +ec2_opts = [ + cfg.StrOpt('admin_user', + help=_("Admin user")), + cfg.StrOpt('admin_password', + help=_("Admin password"), + secret=True), + cfg.StrOpt('admin_tenant_name', + help=_("Admin tenant name")), +] + +CONF = cfg.CONF +CONF.register_opts(ec2_opts) + LOG = logging.getLogger(__name__) @@ -43,7 +58,7 @@ class RequestContext(object): is_admin=None, roles=None, remote_address=None, auth_token=None, user_name=None, project_name=None, overwrite=True, service_catalog=None, api_version=None, - cross_tenants=None, **kwargs): + is_os_admin=None, **kwargs): """Parameters :param overwrite: Set to False to ensure that the greenthread local @@ -55,7 +70,7 @@ class RequestContext(object): """ if kwargs: LOG.warn(_('Arguments dropped when creating context: %s') % - str(kwargs)) + str(kwargs)) self.user_id = user_id self.project_id = project_id @@ -78,7 +93,7 @@ class RequestContext(object): self.project_name = project_name self.is_admin = is_admin # TODO(ft): call policy.check_is_admin if is_admin is None - self.cross_tenants = cross_tenants + self.is_os_admin = is_os_admin self.api_version = api_version if overwrite or not hasattr(local.store, 'context'): self.update_store() @@ -141,6 +156,24 @@ def is_user_context(context): return True +def get_os_admin_context(): + """Create a context to interact with OpenStack as an administrator.""" + # TODO(ft): make an authentification token reusable + keystone = keystone_client.Client( + username=CONF.admin_user, + password=CONF.admin_password, + tenant_name=CONF.admin_tenant_name, + auth_url=CONF.keystone_url, + ) + service_catalog = keystone.service_catalog.get_data() + return RequestContext( + keystone.auth_user_id, + keystone.auth_tenant_id, + auth_token=keystone.auth_token, + service_catalog=service_catalog, + is_os_admin=True) + + def require_context(ctxt): """Raise exception.Forbidden() diff --git a/ec2api/metadata/__init__.py b/ec2api/metadata/__init__.py index 118451bd..fb98514d 100644 --- a/ec2api/metadata/__init__.py +++ b/ec2api/metadata/__init__.py @@ -18,7 +18,6 @@ import posixpath import urlparse import httplib2 -from keystoneclient.v2_0 import client as keystone_client from oslo_config import cfg from oslo_log import log as logging import six @@ -59,13 +58,6 @@ metadata_opts = [ cfg.StrOpt('nova_client_priv_key', default='', help=_("Private key of client certificate.")), - cfg.StrOpt('admin_user', - help=_("Admin user")), - cfg.StrOpt('admin_password', - help=_("Admin password"), - secret=True), - cfg.StrOpt('admin_tenant_name', - help=_("Admin tenant name")), cfg.StrOpt('metadata_proxy_shared_secret', default='', help=_('Shared secret to sign instance-id request'), @@ -162,7 +154,7 @@ class MetadataRequestHandler(wsgi.Application): return req.headers remote_ip = self._get_remote_ip(req) - context = self._get_context() + context = ec2context.get_os_admin_context() instance_id, project_id = ( api.get_os_instance_and_project_id(context, remote_ip)) return { @@ -180,30 +172,13 @@ class MetadataRequestHandler(wsgi.Application): raise exception.EC2MetadataInvalidAddress() return remote_ip - def _get_context(self): - # TODO(ft): make authentification token reusable - keystone = keystone_client.Client( - username=CONF.metadata.admin_user, - password=CONF.metadata.admin_password, - tenant_name=CONF.metadata.admin_tenant_name, - auth_url=CONF.keystone_url, - ) - service_catalog = keystone.service_catalog.get_data() - return ec2context.RequestContext( - keystone.auth_user_id, - keystone.auth_tenant_id, - auth_token=keystone.auth_token, - service_catalog=service_catalog, - is_admin=True, - cross_tenants=True) - def _sign_instance_id(self, instance_id): return hmac.new(CONF.metadata.metadata_proxy_shared_secret, instance_id, hashlib.sha256).hexdigest() def _get_metadata(self, req, path_tokens): - context = self._get_context() + context = ec2context.get_os_admin_context() if req.headers.get('X-Instance-ID'): os_instance_id, project_id, remote_ip = ( self._unpack_request_attributes(req)) diff --git a/ec2api/tests/unit/test_context.py b/ec2api/tests/unit/test_context.py new file mode 100644 index 00000000..2c9e9379 --- /dev/null +++ b/ec2api/tests/unit/test_context.py @@ -0,0 +1,53 @@ +# Copyright 2014 +# The Cloudscaling Group, Inc. +# +# 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. + +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 ec2context + +cfg.CONF.import_opt('keystone_url', 'ec2api.api') + + +class ContextTestCase(test_base.BaseTestCase): + + def setUp(self): + super(ContextTestCase, self).setUp() + conf = config_fixture.Config() + conf.config(admin_user='admin', + admin_password='password', + admin_tenant_name='service') + + @mock.patch('keystoneclient.v2_0.client.Client') + def test_get_os_admin_context(self, keystone): + service_catalog = mock.MagicMock() + 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) + context = ec2context.get_os_admin_context() + self.assertEqual('fake_user_id', context.user_id) + self.assertEqual('fake_project_id', context.project_id) + self.assertEqual('fake_token', context.auth_token) + self.assertEqual('fake_service_catalog', context.service_catalog) + self.assertTrue(context.is_os_admin) + conf = cfg.CONF + keystone.assert_called_with( + username=conf.admin_user, + password=conf.admin_password, + tenant_name=conf.admin_tenant_name, + auth_url=conf.keystone_url) diff --git a/ec2api/tests/unit/test_metadata.py b/ec2api/tests/unit/test_metadata.py index f27eeea6..eafd7460 100644 --- a/ec2api/tests/unit/test_metadata.py +++ b/ec2api/tests/unit/test_metadata.py @@ -39,9 +39,6 @@ class ProxyTestCase(test_base.BaseTestCase): auth_ca_cert=None, nova_client_cert='nova_cert', nova_client_priv_key='nova_priv_key', - admin_user='admin', - admin_password='password', - admin_tenant_name='service', metadata_proxy_shared_secret='secret') @mock.patch('ec2api.metadata.api.get_version_list') @@ -91,7 +88,7 @@ class ProxyTestCase(test_base.BaseTestCase): @mock.patch('ec2api.metadata.api.get_metadata_item') @mock.patch('ec2api.metadata.api.get_os_instance_and_project_id') @mock.patch.object(metadata.MetadataRequestHandler, '_get_remote_ip') - @mock.patch.object(metadata.MetadataRequestHandler, '_get_context') + @mock.patch('ec2api.context.get_os_admin_context') def test_get_metadata_by_ip(self, get_context, get_remote_ip, get_ids, get_metadata_item): get_context.return_value = mock.Mock(project_id='fake_admin_project') @@ -116,7 +113,7 @@ class ProxyTestCase(test_base.BaseTestCase): @mock.patch('ec2api.metadata.api.get_metadata_item') @mock.patch.object(metadata.MetadataRequestHandler, '_unpack_request_attributes') - @mock.patch.object(metadata.MetadataRequestHandler, '_get_context') + @mock.patch('ec2api.context.get_os_admin_context') def test_get_metadata_by_instance_id(self, get_context, unpack_request, get_metadata_item): get_context.return_value = mock.Mock(project_id='fake_admin_project') @@ -243,7 +240,7 @@ class ProxyTestCase(test_base.BaseTestCase): self._proxy_request_test_helper(response_code=302) @mock.patch.object(metadata.MetadataRequestHandler, '_sign_instance_id') - @mock.patch.object(metadata.MetadataRequestHandler, '_get_context') + @mock.patch('ec2api.context.get_os_admin_context') @mock.patch.object(metadata.MetadataRequestHandler, '_get_remote_ip') def test_build_proxy_request_headers(self, get_remote_ip, get_context, sign_instance_id): @@ -300,28 +297,6 @@ class ProxyTestCase(test_base.BaseTestCase): cfg.CONF.set_override('use_forwarded_for', False) self.assertEqual('fake_addr', self.handler._get_remote_ip(req)) - @mock.patch('keystoneclient.v2_0.client.Client') - def test_get_context(self, keystone): - service_catalog = mock.MagicMock() - 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) - context = self.handler._get_context() - self.assertEqual('fake_user_id', context.user_id) - self.assertEqual('fake_project_id', context.project_id) - self.assertEqual('fake_token', context.auth_token) - self.assertEqual('fake_service_catalog', context.service_catalog) - self.assertTrue(context.is_admin) - self.assertTrue(context.cross_tenants) - conf = cfg.CONF - keystone.assert_called_with( - username=conf.metadata.admin_user, - password=conf.metadata.admin_password, - tenant_name=conf.metadata.admin_tenant_name, - auth_url=conf.keystone_url) - def test_unpack_request_attributes(self): sign = ( '97e7709481495f1a3a589e5ee03f8b5d51a3e0196768e300c441b58fe0382f4d') diff --git a/install.sh b/install.sh index d9076914..2cc45e9e 100755 --- a/install.sh +++ b/install.sh @@ -296,9 +296,9 @@ iniset $CONF_FILE keystone_authtoken admin_tenant_name $SERVICE_TENANT iniset $CONF_FILE keystone_authtoken auth_protocol $AUTH_PROTO iniset $CONF_FILE keystone_authtoken auth_port $AUTH_PORT -iniset $CONF_FILE metadata admin_user $SERVICE_USERNAME -iniset $CONF_FILE metadata admin_password $SERVICE_PASSWORD -iniset $CONF_FILE metadata admin_tenant_name $SERVICE_TENANT +iniset $CONF_FILE DEFAULT admin_user $SERVICE_USERNAME +iniset $CONF_FILE DEFAULT admin_password $SERVICE_PASSWORD +iniset $CONF_FILE DEFAULT admin_tenant_name $SERVICE_TENANT if [[ -f "$NOVA_CONF" ]]; then copynovaopt s3_host