Merge "Modify/Move project validation methods to api_utils"

This commit is contained in:
Zuul 2021-05-11 15:23:26 +00:00 committed by Gerrit Code Review
commit 368cddec91
9 changed files with 152 additions and 231 deletions

View File

@ -10,13 +10,23 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from keystoneauth1 import exceptions as ks_exc
from keystoneauth1 import identity
from keystoneauth1 import loading as ka_loading
from keystoneclient import client
from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import strutils from oslo_utils import strutils
import webob import webob
from webob import exc from webob import exc
from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
CONF = cfg.CONF
CONF.import_group('keystone_authtoken',
'keystonemiddleware.auth_token.__init__')
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -142,3 +152,84 @@ def walk_class_hierarchy(clazz, encountered=None):
for subsubclass in walk_class_hierarchy(subclass, encountered): for subsubclass in walk_class_hierarchy(subclass, encountered):
yield subsubclass yield subsubclass
yield subclass yield subclass
def _keystone_client(context, version=(3, 0)):
"""Creates and returns an instance of a generic keystone client.
:param context: The request context
:param version: version of Keystone to request
:return: keystoneclient.client.Client object
"""
if context.system_scope is not None:
auth_plugin = identity.Token(
auth_url=CONF.keystone_authtoken.auth_url,
token=context.auth_token,
system_scope=context.system_scope
)
elif context.domain_id is not None:
auth_plugin = identity.Token(
auth_url=CONF.keystone_authtoken.auth_url,
token=context.auth_token,
domain_id=context.domain_id
)
elif context.project_id is not None:
auth_plugin = identity.Token(
auth_url=CONF.keystone_authtoken.auth_url,
token=context.auth_token,
project_id=context.project_id
)
else:
# We're dealing with an unscoped token from keystone that doesn't
# carry any authoritative power outside of the user simplify proving
# they know their own password. This token isn't associated with any
# authorization target (e.g., system, domain, or project).
auth_plugin = context.get_auth_plugin()
client_session = ka_loading.session.Session().load_from_options(
auth=auth_plugin,
insecure=CONF.keystone_authtoken.insecure,
cacert=CONF.keystone_authtoken.cafile,
key=CONF.keystone_authtoken.keyfile,
cert=CONF.keystone_authtoken.certfile,
split_loggers=CONF.service_user.split_loggers)
return client.Client(auth_url=CONF.keystone_authtoken.auth_url,
session=client_session, version=version)
class GenericProjectInfo(object):
"""Abstraction layer for Keystone V2 and V3 project objects"""
def __init__(self, project_id, project_keystone_api_version,
domain_id=None, name=None, description=None):
self.id = project_id
self.keystone_api_version = project_keystone_api_version
self.domain_id = domain_id
self.name = name
self.description = description
def get_project(context, project_id):
"""Method to verify project exists in keystone"""
keystone = _keystone_client(context)
generic_project = GenericProjectInfo(project_id, keystone.version)
project = keystone.projects.get(project_id)
generic_project.domain_id = project.domain_id
generic_project.name = project.name
generic_project.description = project.description
return generic_project
def validate_project_and_authorize(context, project_id, policy_check=None,
validate_only=False):
try:
target_project = get_project(context, project_id)
if not validate_only:
target_project = {'project_id': target_project.id}
context.authorize(policy_check, target=target_project)
except ks_exc.http.NotFound:
explanation = _("Project with id %s not found." % project_id)
raise exc.HTTPNotFound(explanation=explanation)
except exception.NotAuthorized:
explanation = _("You are not authorized to perform this "
"operation.")
raise exc.HTTPForbidden(explanation=explanation)

View File

@ -16,9 +16,9 @@
"""The resource filters api.""" """The resource filters api."""
from http import HTTPStatus from http import HTTPStatus
from keystoneauth1 import exceptions as ks_exc
from webob import exc from webob import exc
from cinder.api import api_utils as utils
from cinder.api import microversions as mv from cinder.api import microversions as mv
from cinder.api.openstack import wsgi from cinder.api.openstack import wsgi
from cinder.api.schemas import default_types from cinder.api.schemas import default_types
@ -29,7 +29,6 @@ from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
from cinder import objects from cinder import objects
from cinder.policies import default_types as policy from cinder.policies import default_types as policy
from cinder import quota_utils
class DefaultTypesController(wsgi.Controller): class DefaultTypesController(wsgi.Controller):
@ -37,22 +36,6 @@ class DefaultTypesController(wsgi.Controller):
_view_builder_class = default_types_view.ViewBuilder _view_builder_class = default_types_view.ViewBuilder
def _validate_project_and_authorize(self, context, project_id,
policy_check):
try:
target_project = quota_utils.get_project_hierarchy(context,
project_id)
target_project = {'project_id': target_project.id,
'domain_id': target_project.domain_id}
context.authorize(policy_check, target=target_project)
except ks_exc.http.NotFound:
explanation = _("Project with id %s not found." % project_id)
raise exc.HTTPNotFound(explanation=explanation)
except exception.NotAuthorized:
explanation = _("You are not authorized to perform this "
"operation.")
raise exc.HTTPForbidden(explanation=explanation)
@wsgi.response(HTTPStatus.OK) @wsgi.response(HTTPStatus.OK)
@wsgi.Controller.api_version(mv.DEFAULT_TYPE_OVERRIDES) @wsgi.Controller.api_version(mv.DEFAULT_TYPE_OVERRIDES)
@validation.schema(default_types.create_or_update) @validation.schema(default_types.create_or_update)
@ -63,7 +46,7 @@ class DefaultTypesController(wsgi.Controller):
project_id = id project_id = id
volume_type_id = body['default_type']['volume_type'] volume_type_id = body['default_type']['volume_type']
self._validate_project_and_authorize(context, project_id, utils.validate_project_and_authorize(context, project_id,
policy.CREATE_UPDATE_POLICY) policy.CREATE_UPDATE_POLICY)
try: try:
volume_type_id = objects.VolumeType.get_by_name_or_id( volume_type_id = objects.VolumeType.get_by_name_or_id(
@ -85,7 +68,7 @@ class DefaultTypesController(wsgi.Controller):
context = req.environ['cinder.context'] context = req.environ['cinder.context']
project_id = id project_id = id
self._validate_project_and_authorize(context, project_id, utils.validate_project_and_authorize(context, project_id,
policy.GET_POLICY) policy.GET_POLICY)
default_type = db.project_default_volume_type_get(context, project_id) default_type = db.project_default_volume_type_get(context, project_id)
if not default_type: if not default_type:
@ -117,7 +100,7 @@ class DefaultTypesController(wsgi.Controller):
context = req.environ['cinder.context'] context = req.environ['cinder.context']
project_id = id project_id = id
self._validate_project_and_authorize(context, project_id, utils.validate_project_and_authorize(context, project_id,
policy.DELETE_POLICY) policy.DELETE_POLICY)
db.project_default_volume_type_unset(context, id) db.project_default_volume_type_unset(context, id)

View File

@ -12,9 +12,6 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from keystoneauth1 import identity
from keystoneauth1 import loading as ka_loading
from keystoneclient import client
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
@ -27,23 +24,6 @@ CONF.import_group('keystone_authtoken',
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class GenericProjectInfo(object):
"""Abstraction layer for Keystone V2 and V3 project objects"""
def __init__(self, project_id, project_keystone_api_version,
project_parent_id=None,
project_subtree=None,
project_parent_tree=None,
is_admin_project=False,
domain_id=None):
self.id = project_id
self.domain_id = domain_id
self.keystone_api_version = project_keystone_api_version
self.parent_id = project_parent_id
self.subtree = project_subtree
self.parents = project_parent_tree
self.is_admin_project = is_admin_project
def get_volume_type_reservation(ctxt, volume, type_id, def get_volume_type_reservation(ctxt, volume, type_id,
reserve_vol_type_only=False, reserve_vol_type_only=False,
negative=False): negative=False):
@ -101,82 +81,6 @@ def _filter_domain_id_from_parents(domain_id, tree):
return new_tree return new_tree
def get_project_hierarchy(context, project_id, subtree_as_ids=False,
parents_as_ids=False, is_admin_project=False):
"""A Helper method to get the project hierarchy.
Along with hierarchical multitenancy in keystone API v3, projects can be
hierarchically organized. Therefore, we need to know the project
hierarchy, if any, in order to do default volume type operations properly.
"""
keystone = _keystone_client(context)
generic_project = GenericProjectInfo(project_id, keystone.version)
if keystone.version == 'v3':
project = keystone.projects.get(project_id,
subtree_as_ids=subtree_as_ids,
parents_as_ids=parents_as_ids)
generic_project.parent_id = None
generic_project.domain_id = project.domain_id
if project.parent_id != project.domain_id:
generic_project.parent_id = project.parent_id
generic_project.subtree = (
project.subtree if subtree_as_ids else None)
generic_project.parents = None
if parents_as_ids:
generic_project.parents = _filter_domain_id_from_parents(
project.domain_id, project.parents)
generic_project.is_admin_project = is_admin_project
return generic_project
def _keystone_client(context, version=(3, 0)):
"""Creates and returns an instance of a generic keystone client.
:param context: The request context
:param version: version of Keystone to request
:return: keystoneclient.client.Client object
"""
if context.system_scope is not None:
auth_plugin = identity.Token(
auth_url=CONF.keystone_authtoken.auth_url,
token=context.auth_token,
system_scope=context.system_scope
)
elif context.domain_id is not None:
auth_plugin = identity.Token(
auth_url=CONF.keystone_authtoken.auth_url,
token=context.auth_token,
domain_id=context.domain_id
)
elif context.project_id is not None:
auth_plugin = identity.Token(
auth_url=CONF.keystone_authtoken.auth_url,
token=context.auth_token,
project_id=context.project_id
)
else:
# We're dealing with an unscoped token from keystone that doesn't
# carry any authoritative power outside of the user simplify proving
# they know their own password. This token isn't associated with any
# authorization target (e.g., system, domain, or project).
auth_plugin = context.get_auth_plugin()
client_session = ka_loading.session.Session().load_from_options(
auth=auth_plugin,
insecure=CONF.keystone_authtoken.insecure,
cacert=CONF.keystone_authtoken.cafile,
key=CONF.keystone_authtoken.keyfile,
cert=CONF.keystone_authtoken.certfile,
split_loggers=CONF.service_user.split_loggers)
return client.Client(auth_url=CONF.keystone_authtoken.auth_url,
session=client_session, version=version)
OVER_QUOTA_RESOURCE_EXCEPTIONS = {'snapshots': exception.SnapshotLimitExceeded, OVER_QUOTA_RESOURCE_EXCEPTIONS = {'snapshots': exception.SnapshotLimitExceeded,
'backups': exception.BackupLimitExceeded, 'backups': exception.BackupLimitExceeded,
'volumes': exception.VolumeLimitExceeded, 'volumes': exception.VolumeLimitExceeded,

View File

@ -35,7 +35,7 @@ class DefaultVolumeTypesTest(functional_helpers._FunctionalTestBase):
_keystone_client.version = 'v3' _keystone_client.version = 'v3'
_keystone_client.projects.get.side_effect = self._get_project _keystone_client.projects.get.side_effect = self._get_project
_keystone_client_get = mock.patch( _keystone_client_get = mock.patch(
'cinder.quota_utils._keystone_client', 'cinder.api.api_utils._keystone_client',
lambda *args, **kwargs: _keystone_client) lambda *args, **kwargs: _keystone_client)
_keystone_client_get.start() _keystone_client_get.start()
self.addCleanup(_keystone_client_get.stop) self.addCleanup(_keystone_client_get.stop)
@ -44,14 +44,11 @@ class DefaultVolumeTypesTest(functional_helpers._FunctionalTestBase):
return self.project return self.project
class FakeProject(object): class FakeProject(object):
_dom_id = uuid.uuid4().hex def __init__(self, name=None):
def __init__(self, parent_id=None):
self.id = uuid.uuid4().hex self.id = uuid.uuid4().hex
self.parent_id = parent_id self.name = name
self.domain_id = self._dom_id self.description = 'fake project description'
self.subtree = None self.domain_id = 'default'
self.parents = None
@mock.patch.object(context.RequestContext, 'authorize') @mock.patch.object(context.RequestContext, 'authorize')
def test_default_type_set(self, mock_authorize): def test_default_type_set(self, mock_authorize):

View File

@ -99,7 +99,7 @@ class QuotaSetsControllerTestBase(test.TestCase):
self.req.environ['cinder.context'].project_id = uuid.uuid4().hex self.req.environ['cinder.context'].project_id = uuid.uuid4().hex
get_patcher = mock.patch('cinder.quota_utils.get_project_hierarchy', get_patcher = mock.patch('cinder.api.api_utils.get_project',
self._get_project) self._get_project)
get_patcher.start() get_patcher.start()
self.addCleanup(get_patcher.stop) self.addCleanup(get_patcher.stop)

View File

@ -79,7 +79,7 @@ class DefaultVolumeTypesApiTest(test.TestCase):
self.type2 = self._create_volume_type( self.type2 = self._create_volume_type(
self.ctxt, 'volume_type2') self.ctxt, 'volume_type2')
get_patcher = mock.patch('cinder.quota_utils.get_project_hierarchy', get_patcher = mock.patch('cinder.api.api_utils.get_project',
self._get_project) self._get_project)
get_patcher.start() get_patcher.start()
self.addCleanup(get_patcher.stop) self.addCleanup(get_patcher.stop)

View File

@ -15,6 +15,7 @@
from http import HTTPStatus from http import HTTPStatus
from unittest import mock from unittest import mock
import uuid
from cinder.api import microversions as mv from cinder.api import microversions as mv
from cinder import db from cinder import db
@ -38,7 +39,7 @@ class DefaultVolumeTypesPolicyTests(test_base.CinderPolicyTests):
_keystone_client.version = 'v3' _keystone_client.version = 'v3'
_keystone_client.projects.get.side_effect = self._get_project _keystone_client.projects.get.side_effect = self._get_project
_keystone_client_get = mock.patch( _keystone_client_get = mock.patch(
'cinder.quota_utils._keystone_client', 'cinder.api.api_utils._keystone_client',
lambda *args, **kwargs: _keystone_client) lambda *args, **kwargs: _keystone_client)
_keystone_client_get.start() _keystone_client_get.start()
self.addCleanup(_keystone_client_get.stop) self.addCleanup(_keystone_client_get.stop)
@ -47,14 +48,11 @@ class DefaultVolumeTypesPolicyTests(test_base.CinderPolicyTests):
return self.project return self.project
class FakeProject(object): class FakeProject(object):
_dom_id = fake_constants.DOMAIN_ID def __init__(self, name=None):
self.id = uuid.uuid4().hex
def __init__(self, parent_id=None): self.name = name
self.id = fake_constants.PROJECT_ID self.description = 'fake project description'
self.parent_id = parent_id self.domain_id = 'default'
self.domain_id = self._dom_id
self.subtree = None
self.parents = None
def test_system_admin_can_set_default(self): def test_system_admin_can_set_default(self):
system_admin_context = self.system_admin_context system_admin_context = self.system_admin_context

View File

@ -18,6 +18,7 @@ from unittest import mock
from oslo_config import cfg from oslo_config import cfg
from oslo_config import fixture as config_fixture from oslo_config import fixture as config_fixture
from cinder.api import api_utils
from cinder import context from cinder import context
from cinder import exception from cinder import exception
from cinder import quota_utils from cinder import quota_utils
@ -29,13 +30,6 @@ CONF = cfg.CONF
class QuotaUtilsTest(test.TestCase): class QuotaUtilsTest(test.TestCase):
class FakeProject(object):
def __init__(self, id='foo', parent_id=None):
self.id = id
self.parent_id = parent_id
self.subtree = None
self.parents = None
self.domain_id = 'default'
def setUp(self): def setUp(self):
super(QuotaUtilsTest, self).setUp() super(QuotaUtilsTest, self).setUp()
@ -49,7 +43,7 @@ class QuotaUtilsTest(test.TestCase):
@mock.patch('keystoneauth1.session.Session') @mock.patch('keystoneauth1.session.Session')
def test_keystone_client_instantiation(self, ksclient_session, def test_keystone_client_instantiation(self, ksclient_session,
ksclient_class): ksclient_class):
quota_utils._keystone_client(self.context) api_utils._keystone_client(self.context)
ksclient_class.assert_called_once_with(auth_url=self.auth_url, ksclient_class.assert_called_once_with(auth_url=self.auth_url,
session=ksclient_session(), session=ksclient_session(),
version=(3, 0)) version=(3, 0))
@ -61,7 +55,7 @@ class QuotaUtilsTest(test.TestCase):
self, ks_token, ksclient_session, ksclient_class): self, ks_token, ksclient_session, ksclient_class):
system_context = context.RequestContext( system_context = context.RequestContext(
'fake_user', 'fake_proj_id', system_scope='all') 'fake_user', 'fake_proj_id', system_scope='all')
quota_utils._keystone_client(system_context) api_utils._keystone_client(system_context)
ks_token.assert_called_once_with( ks_token.assert_called_once_with(
auth_url=self.auth_url, token=system_context.auth_token, auth_url=self.auth_url, token=system_context.auth_token,
system_scope=system_context.system_scope) system_scope=system_context.system_scope)
@ -73,7 +67,7 @@ class QuotaUtilsTest(test.TestCase):
self, ks_token, ksclient_session, ksclient_class): self, ks_token, ksclient_session, ksclient_class):
domain_context = context.RequestContext( domain_context = context.RequestContext(
'fake_user', 'fake_proj_id', domain_id='default') 'fake_user', 'fake_proj_id', domain_id='default')
quota_utils._keystone_client(domain_context) api_utils._keystone_client(domain_context)
ks_token.assert_called_once_with( ks_token.assert_called_once_with(
auth_url=self.auth_url, token=domain_context.auth_token, auth_url=self.auth_url, token=domain_context.auth_token,
domain_id=domain_context.domain_id) domain_id=domain_context.domain_id)
@ -85,51 +79,11 @@ class QuotaUtilsTest(test.TestCase):
self, ks_token, ksclient_session, ksclient_class): self, ks_token, ksclient_session, ksclient_class):
project_context = context.RequestContext( project_context = context.RequestContext(
'fake_user', project_id=fake.PROJECT_ID) 'fake_user', project_id=fake.PROJECT_ID)
quota_utils._keystone_client(project_context) api_utils._keystone_client(project_context)
ks_token.assert_called_once_with( ks_token.assert_called_once_with(
auth_url=self.auth_url, token=project_context.auth_token, auth_url=self.auth_url, token=project_context.auth_token,
project_id=project_context.project_id) project_id=project_context.project_id)
@mock.patch('keystoneclient.client.Client')
def test_get_project_keystoneclient_v2(self, ksclient_class):
keystoneclient = ksclient_class.return_value
keystoneclient.version = 'v2.0'
expected_project = quota_utils.GenericProjectInfo(
self.context.project_id, 'v2.0')
project = quota_utils.get_project_hierarchy(
self.context, self.context.project_id)
self.assertEqual(expected_project.__dict__, project.__dict__)
@mock.patch('keystoneclient.client.Client')
def test_get_project_keystoneclient_v3(self, ksclient_class):
keystoneclient = ksclient_class.return_value
keystoneclient.version = 'v3'
returned_project = self.FakeProject(self.context.project_id, 'bar')
del returned_project.subtree
keystoneclient.projects.get.return_value = returned_project
expected_project = quota_utils.GenericProjectInfo(
self.context.project_id, 'v3', 'bar', domain_id='default')
project = quota_utils.get_project_hierarchy(
self.context, self.context.project_id)
self.assertEqual(expected_project.__dict__, project.__dict__)
@mock.patch('keystoneclient.client.Client')
def test_get_project_keystoneclient_v3_with_subtree(self, ksclient_class):
keystoneclient = ksclient_class.return_value
keystoneclient.version = 'v3'
returned_project = self.FakeProject(self.context.project_id, 'bar')
subtree_dict = {'baz': {'quux': None}}
returned_project.subtree = subtree_dict
keystoneclient.projects.get.return_value = returned_project
expected_project = quota_utils.GenericProjectInfo(
self.context.project_id, 'v3', 'bar', subtree_dict,
domain_id='default')
project = quota_utils.get_project_hierarchy(
self.context, self.context.project_id, subtree_as_ids=True)
keystoneclient.projects.get.assert_called_once_with(
self.context.project_id, parents_as_ids=False, subtree_as_ids=True)
self.assertEqual(expected_project.__dict__, project.__dict__)
def _setup_mock_ksclient(self, mock_client, version='v3', def _setup_mock_ksclient(self, mock_client, version='v3',
subtree=None, parents=None): subtree=None, parents=None):
keystoneclient = mock_client.return_value keystoneclient = mock_client.return_value
@ -141,50 +95,6 @@ class QuotaUtilsTest(test.TestCase):
proj.parent_id = next(iter(parents.keys())) proj.parent_id = next(iter(parents.keys()))
keystoneclient.projects.get.return_value = proj keystoneclient.projects.get.return_value = proj
@mock.patch('keystoneclient.client.Client')
def test__filter_domain_id_from_parents_domain_as_parent(
self, mock_client):
# Test with a top level project (domain is direct parent)
self._setup_mock_ksclient(mock_client, parents={'default': None})
project = quota_utils.get_project_hierarchy(
self.context, self.context.project_id, parents_as_ids=True)
self.assertIsNone(project.parent_id)
self.assertIsNone(project.parents)
@mock.patch('keystoneclient.client.Client')
def test__filter_domain_id_from_parents_domain_as_grandparent(
self, mock_client):
# Test with a child project (domain is more than a parent)
self._setup_mock_ksclient(mock_client,
parents={'bar': {'default': None}})
project = quota_utils.get_project_hierarchy(
self.context, self.context.project_id, parents_as_ids=True)
self.assertEqual('bar', project.parent_id)
self.assertEqual({'bar': None}, project.parents)
@mock.patch('keystoneclient.client.Client')
def test__filter_domain_id_from_parents_no_domain_in_parents(
self, mock_client):
# Test that if top most parent is not a domain (to simulate an older
# keystone version) nothing gets removed from the tree
parents = {'bar': {'foo': None}}
self._setup_mock_ksclient(mock_client, parents=parents)
project = quota_utils.get_project_hierarchy(
self.context, self.context.project_id, parents_as_ids=True)
self.assertEqual('bar', project.parent_id)
self.assertEqual(parents, project.parents)
@mock.patch('keystoneclient.client.Client')
def test__filter_domain_id_from_parents_no_parents(
self, mock_client):
# Test that if top no parents are present (to simulate an older
# keystone version) things don't blow up
self._setup_mock_ksclient(mock_client)
project = quota_utils.get_project_hierarchy(
self.context, self.context.project_id, parents_as_ids=True)
self.assertIsNone(project.parent_id)
self.assertIsNone(project.parents)
def _process_reserve_over_quota(self, overs, usages, quotas, def _process_reserve_over_quota(self, overs, usages, quotas,
expected_ex, expected_ex,
resource='volumes'): resource='volumes'):

View File

@ -24,6 +24,7 @@ import webob.exc
import cinder import cinder
from cinder.api import api_utils from cinder.api import api_utils
from cinder import context
from cinder import exception from cinder import exception
from cinder.tests.unit import fake_constants as fake from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import test from cinder.tests.unit import test
@ -1415,3 +1416,40 @@ class LimitOperationsTestCase(test.TestCase):
mock_exec.assert_called_once_with(mocked_semaphore.__enter__) mock_exec.assert_called_once_with(mocked_semaphore.__enter__)
mocked_semaphore.__exit__.assert_not_called() mocked_semaphore.__exit__.assert_not_called()
mocked_semaphore.__exit__.assert_called_once_with(None, None, None) mocked_semaphore.__exit__.assert_called_once_with(None, None, None)
class TestKeystoneProjectGet(test.TestCase):
class FakeProject(object):
def __init__(self, id='foo', name=None):
self.id = id
self.name = name
self.description = 'fake project description'
self.domain_id = 'default'
@mock.patch('keystoneclient.client.Client')
def test_get_project_keystoneclient_v2(self, ksclient_class):
self.context = context.RequestContext('fake_user', 'fake_proj_id')
keystoneclient = ksclient_class.return_value
keystoneclient.version = 'v2.0'
returned_project = self.FakeProject(self.context.project_id, 'bar')
keystoneclient.projects.get.return_value = returned_project
expected_project = api_utils.GenericProjectInfo(
self.context.project_id, 'v2.0', domain_id='default', name='bar',
description='fake project description')
project = api_utils.get_project(
self.context, self.context.project_id)
self.assertEqual(expected_project.__dict__, project.__dict__)
@mock.patch('keystoneclient.client.Client')
def test_get_project_keystoneclient_v3(self, ksclient_class):
self.context = context.RequestContext('fake_user', 'fake_proj_id')
keystoneclient = ksclient_class.return_value
keystoneclient.version = 'v3'
returned_project = self.FakeProject(self.context.project_id, 'bar')
keystoneclient.projects.get.return_value = returned_project
expected_project = api_utils.GenericProjectInfo(
self.context.project_id, 'v3', domain_id='default', name='bar',
description='fake project description')
project = api_utils.get_project(
self.context, self.context.project_id)
self.assertEqual(expected_project.__dict__, project.__dict__)