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
This commit is contained in:
Feodor Tersin 2015-03-09 11:33:31 +03:00
parent 0275db9401
commit 5480e9b850
7 changed files with 101 additions and 65 deletions

View File

@ -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"

View File

@ -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):

View File

@ -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()

View File

@ -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))

View File

@ -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)

View File

@ -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')

View File

@ -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