Merge "Update policies related to user visible extra specs"
This commit is contained in:
commit
c1fd778a17
@ -31,17 +31,29 @@ GET_POLICY = "volume_extension:types_extra_specs:show"
|
|||||||
READ_SENSITIVE_POLICY = "volume_extension:types_extra_specs:read_sensitive"
|
READ_SENSITIVE_POLICY = "volume_extension:types_extra_specs:read_sensitive"
|
||||||
UPDATE_POLICY = "volume_extension:types_extra_specs:update"
|
UPDATE_POLICY = "volume_extension:types_extra_specs:update"
|
||||||
|
|
||||||
|
deprecated_get_all_policy = base.CinderDeprecatedRule(
|
||||||
|
name=GET_ALL_POLICY,
|
||||||
|
check_str=""
|
||||||
|
)
|
||||||
|
|
||||||
|
deprecated_get_policy = base.CinderDeprecatedRule(
|
||||||
|
name=GET_POLICY,
|
||||||
|
check_str=""
|
||||||
|
)
|
||||||
|
|
||||||
type_extra_specs_policies = [
|
type_extra_specs_policies = [
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=GET_ALL_POLICY,
|
name=GET_ALL_POLICY,
|
||||||
check_str="",
|
check_str=base.SYSTEM_READER_OR_PROJECT_READER,
|
||||||
description="List type extra specs.",
|
description="List type extra specs.",
|
||||||
operations=[
|
operations=[
|
||||||
{
|
{
|
||||||
'method': 'GET',
|
'method': 'GET',
|
||||||
'path': '/types/{type_id}/extra_specs'
|
'path': '/types/{type_id}/extra_specs'
|
||||||
}
|
}
|
||||||
]),
|
],
|
||||||
|
deprecated_rule=deprecated_get_all_policy,
|
||||||
|
),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=CREATE_POLICY,
|
name=CREATE_POLICY,
|
||||||
check_str=base.RULE_ADMIN_API,
|
check_str=base.RULE_ADMIN_API,
|
||||||
@ -51,17 +63,20 @@ type_extra_specs_policies = [
|
|||||||
'method': 'POST',
|
'method': 'POST',
|
||||||
'path': '/types/{type_id}/extra_specs'
|
'path': '/types/{type_id}/extra_specs'
|
||||||
}
|
}
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=GET_POLICY,
|
name=GET_POLICY,
|
||||||
check_str="",
|
check_str=base.SYSTEM_READER_OR_PROJECT_READER,
|
||||||
description="Show one specified type extra specs.",
|
description="Show one specified type extra specs.",
|
||||||
operations=[
|
operations=[
|
||||||
{
|
{
|
||||||
'method': 'GET',
|
'method': 'GET',
|
||||||
'path': '/types/{type_id}/extra_specs/{extra_spec_key}'
|
'path': '/types/{type_id}/extra_specs/{extra_spec_key}'
|
||||||
}
|
}
|
||||||
]),
|
],
|
||||||
|
deprecated_rule=deprecated_get_policy,
|
||||||
|
),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=READ_SENSITIVE_POLICY,
|
name=READ_SENSITIVE_POLICY,
|
||||||
check_str=base.RULE_ADMIN_API,
|
check_str=base.RULE_ADMIN_API,
|
||||||
@ -87,7 +102,8 @@ type_extra_specs_policies = [
|
|||||||
'method': 'GET',
|
'method': 'GET',
|
||||||
'path': '/types/{type_id}/extra_specs/{extra_spec_key}'
|
'path': '/types/{type_id}/extra_specs/{extra_spec_key}'
|
||||||
}
|
}
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=UPDATE_POLICY,
|
name=UPDATE_POLICY,
|
||||||
check_str=base.RULE_ADMIN_API,
|
check_str=base.RULE_ADMIN_API,
|
||||||
@ -97,7 +113,8 @@ type_extra_specs_policies = [
|
|||||||
'method': 'PUT',
|
'method': 'PUT',
|
||||||
'path': '/types/{type_id}/extra_specs/{extra_spec_key}'
|
'path': '/types/{type_id}/extra_specs/{extra_spec_key}'
|
||||||
}
|
}
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=DELETE_POLICY,
|
name=DELETE_POLICY,
|
||||||
check_str=base.RULE_ADMIN_API,
|
check_str=base.RULE_ADMIN_API,
|
||||||
@ -107,7 +124,8 @@ type_extra_specs_policies = [
|
|||||||
'method': 'DELETE',
|
'method': 'DELETE',
|
||||||
'path': '/types/{type_id}/extra_specs/{extra_spec_key}'
|
'path': '/types/{type_id}/extra_specs/{extra_spec_key}'
|
||||||
}
|
}
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,6 +50,10 @@ deprecated_manage_policy = base.CinderDeprecatedRule(
|
|||||||
'policies that separately govern POST, PUT, and DELETE '
|
'policies that separately govern POST, PUT, and DELETE '
|
||||||
'operations.'),
|
'operations.'),
|
||||||
)
|
)
|
||||||
|
deprecated_extra_spec_policy = base.CinderDeprecatedRule(
|
||||||
|
name=EXTRA_SPEC_POLICY,
|
||||||
|
check_str=base.RULE_ADMIN_API
|
||||||
|
)
|
||||||
deprecated_encryption_create_policy = base.CinderDeprecatedRule(
|
deprecated_encryption_create_policy = base.CinderDeprecatedRule(
|
||||||
name=CREATE_ENCRYPTION_POLICY,
|
name=CREATE_ENCRYPTION_POLICY,
|
||||||
# TODO: change to base.RULE_ADMIN_API in Yoga & remove dep_reason
|
# TODO: change to base.RULE_ADMIN_API in Yoga & remove dep_reason
|
||||||
@ -150,7 +154,7 @@ volume_type_policies = [
|
|||||||
),
|
),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=EXTRA_SPEC_POLICY,
|
name=EXTRA_SPEC_POLICY,
|
||||||
check_str=base.RULE_ADMIN_API,
|
check_str=base.SYSTEM_READER_OR_PROJECT_READER,
|
||||||
description=(
|
description=(
|
||||||
"Include the volume type's extra_specs attribute in the volume "
|
"Include the volume type's extra_specs attribute in the volume "
|
||||||
"type list or show requests. The ability to make these calls "
|
"type list or show requests. The ability to make these calls "
|
||||||
@ -164,7 +168,9 @@ volume_type_policies = [
|
|||||||
'method': 'GET',
|
'method': 'GET',
|
||||||
'path': '/types'
|
'path': '/types'
|
||||||
}
|
}
|
||||||
]),
|
],
|
||||||
|
deprecated_rule=deprecated_extra_spec_policy,
|
||||||
|
),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault(
|
||||||
name=QOS_POLICY,
|
name=QOS_POLICY,
|
||||||
check_str=base.RULE_ADMIN_API,
|
check_str=base.RULE_ADMIN_API,
|
||||||
|
@ -132,7 +132,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
self.mock_object(cinder.db, 'volume_type_extra_specs_delete')
|
self.mock_object(cinder.db, 'volume_type_extra_specs_delete')
|
||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
req = fakes.HTTPRequest.blank(self.api_path + '/key5')
|
req = fakes.HTTPRequest.blank(self.api_path + '/key5',
|
||||||
|
use_admin_context=True)
|
||||||
self.controller.delete(req, fake.VOLUME_ID, 'key5')
|
self.controller.delete(req, fake.VOLUME_ID, 'key5')
|
||||||
self.assertEqual(1, len(self.notifier.notifications))
|
self.assertEqual(1, len(self.notifier.notifications))
|
||||||
self.assertIn('created_at', self.notifier.notifications[0]['payload'])
|
self.assertIn('created_at', self.notifier.notifications[0]['payload'])
|
||||||
@ -144,7 +145,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
side_effect=exception.VolumeTypeExtraSpecsNotFound(
|
side_effect=exception.VolumeTypeExtraSpecsNotFound(
|
||||||
"Not Found"))
|
"Not Found"))
|
||||||
|
|
||||||
req = fakes.HTTPRequest.blank(self.api_path + '/key6')
|
req = fakes.HTTPRequest.blank(self.api_path + '/key6',
|
||||||
|
use_admin_context=True)
|
||||||
self.assertRaises(exception.VolumeTypeExtraSpecsNotFound,
|
self.assertRaises(exception.VolumeTypeExtraSpecsNotFound,
|
||||||
self.controller.delete, req, fake.VOLUME_ID, 'key6')
|
self.controller.delete, req, fake.VOLUME_ID, 'key6')
|
||||||
|
|
||||||
@ -155,7 +157,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
body = {"extra_specs": {"key1": "value1"}}
|
body = {"extra_specs": {"key1": "value1"}}
|
||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
req = fakes.HTTPRequest.blank(self.api_path)
|
req = fakes.HTTPRequest.blank(self.api_path,
|
||||||
|
use_admin_context=True)
|
||||||
res_dict = self.controller.create(req, fake.VOLUME_ID, body=body)
|
res_dict = self.controller.create(req, fake.VOLUME_ID, body=body)
|
||||||
self.assertEqual(1, len(self.notifier.notifications))
|
self.assertEqual(1, len(self.notifier.notifications))
|
||||||
self.assertIn('created_at', self.notifier.notifications[0]['payload'])
|
self.assertIn('created_at', self.notifier.notifications[0]['payload'])
|
||||||
@ -179,7 +182,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
body = {"extra_specs": {"image_service:store_id": "cheap"}}
|
body = {"extra_specs": {"image_service:store_id": "cheap"}}
|
||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
req = fakes.HTTPRequest.blank(self.api_path)
|
req = fakes.HTTPRequest.blank(self.api_path,
|
||||||
|
use_admin_context=True)
|
||||||
res_dict = self.controller.create(req, fake.VOLUME_ID, body=body)
|
res_dict = self.controller.create(req, fake.VOLUME_ID, body=body)
|
||||||
self.assertEqual(1, len(self.notifier.notifications))
|
self.assertEqual(1, len(self.notifier.notifications))
|
||||||
self.assertIn('created_at', self.notifier.notifications[0]['payload'])
|
self.assertIn('created_at', self.notifier.notifications[0]['payload'])
|
||||||
@ -199,7 +203,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
body = {"extra_specs": {"image_service:store_id": "fast"}}
|
body = {"extra_specs": {"image_service:store_id": "fast"}}
|
||||||
req = fakes.HTTPRequest.blank(self.api_path)
|
req = fakes.HTTPRequest.blank(self.api_path,
|
||||||
|
use_admin_context=True)
|
||||||
self.assertRaises(cinder.exception.GlanceStoreNotFound,
|
self.assertRaises(cinder.exception.GlanceStoreNotFound,
|
||||||
self.controller.create,
|
self.controller.create,
|
||||||
req, fake.VOLUME_ID, body=body)
|
req, fake.VOLUME_ID, body=body)
|
||||||
@ -216,7 +221,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
body = {"extra_specs": {"image_service:store_id": "read_only_store"}}
|
body = {"extra_specs": {"image_service:store_id": "read_only_store"}}
|
||||||
req = fakes.HTTPRequest.blank(self.api_path)
|
req = fakes.HTTPRequest.blank(self.api_path,
|
||||||
|
use_admin_context=True)
|
||||||
self.assertRaises(cinder.exception.GlanceStoreReadOnly,
|
self.assertRaises(cinder.exception.GlanceStoreReadOnly,
|
||||||
self.controller.create,
|
self.controller.create,
|
||||||
req, fake.VOLUME_ID, body=body)
|
req, fake.VOLUME_ID, body=body)
|
||||||
@ -236,7 +242,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
|
|
||||||
req = fakes.HTTPRequest.blank(self.api_path)
|
req = fakes.HTTPRequest.blank(self.api_path,
|
||||||
|
use_admin_context=True)
|
||||||
res_dict = self.controller.create(req, fake.VOLUME_ID, body=body)
|
res_dict = self.controller.create(req, fake.VOLUME_ID, body=body)
|
||||||
self.assertEqual(1, len(self.notifier.notifications))
|
self.assertEqual(1, len(self.notifier.notifications))
|
||||||
self.assertEqual('value1',
|
self.assertEqual('value1',
|
||||||
@ -259,7 +266,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
|
|
||||||
req = fakes.HTTPRequest.blank(self.api_path)
|
req = fakes.HTTPRequest.blank(self.api_path,
|
||||||
|
use_admin_context=True)
|
||||||
res_dict = self.controller.create(req, fake.VOLUME_ID, body=body)
|
res_dict = self.controller.create(req, fake.VOLUME_ID, body=body)
|
||||||
self.assertEqual(1, len(self.notifier.notifications))
|
self.assertEqual(1, len(self.notifier.notifications))
|
||||||
self.assertEqual('value1',
|
self.assertEqual('value1',
|
||||||
@ -290,7 +298,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
req = fakes.HTTPRequest.blank(
|
req = fakes.HTTPRequest.blank(
|
||||||
self.api_path + "/image_service:store_id")
|
self.api_path + "/image_service:store_id",
|
||||||
|
use_admin_context=True)
|
||||||
res_dict = self.controller.update(req, fake.VOLUME_ID,
|
res_dict = self.controller.update(req, fake.VOLUME_ID,
|
||||||
"image_service:store_id",
|
"image_service:store_id",
|
||||||
body=body)
|
body=body)
|
||||||
@ -321,7 +330,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
req = fakes.HTTPRequest.blank(
|
req = fakes.HTTPRequest.blank(
|
||||||
self.api_path + "/image_service:store_id")
|
self.api_path + "/image_service:store_id",
|
||||||
|
use_admin_context=True)
|
||||||
self.assertRaises(cinder.exception.GlanceStoreNotFound,
|
self.assertRaises(cinder.exception.GlanceStoreNotFound,
|
||||||
self.controller.update,
|
self.controller.update,
|
||||||
req, fake.VOLUME_ID,
|
req, fake.VOLUME_ID,
|
||||||
@ -349,7 +359,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
req = fakes.HTTPRequest.blank(
|
req = fakes.HTTPRequest.blank(
|
||||||
self.api_path + "/image_service:store_id")
|
self.api_path + "/image_service:store_id",
|
||||||
|
use_admin_context=True)
|
||||||
self.assertRaises(cinder.exception.GlanceStoreReadOnly,
|
self.assertRaises(cinder.exception.GlanceStoreReadOnly,
|
||||||
self.controller.update,
|
self.controller.update,
|
||||||
req, fake.VOLUME_ID,
|
req, fake.VOLUME_ID,
|
||||||
@ -363,7 +374,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
body = {"key1": "value1"}
|
body = {"key1": "value1"}
|
||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
req = fakes.HTTPRequest.blank(self.api_path + '/key1')
|
req = fakes.HTTPRequest.blank(self.api_path + '/key1',
|
||||||
|
use_admin_context=True)
|
||||||
res_dict = self.controller.update(req, fake.VOLUME_ID, 'key1',
|
res_dict = self.controller.update(req, fake.VOLUME_ID, 'key1',
|
||||||
body=body)
|
body=body)
|
||||||
self.assertEqual(1, len(self.notifier.notifications))
|
self.assertEqual(1, len(self.notifier.notifications))
|
||||||
@ -378,7 +390,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
return_create_volume_type_extra_specs)
|
return_create_volume_type_extra_specs)
|
||||||
body = {"key1": "value1", "key2": "value2"}
|
body = {"key1": "value1", "key2": "value2"}
|
||||||
|
|
||||||
req = fakes.HTTPRequest.blank(self.api_path + '/key1')
|
req = fakes.HTTPRequest.blank(self.api_path + '/key1',
|
||||||
|
use_admin_context=True)
|
||||||
self.assertRaises(exception.ValidationError, self.controller.update,
|
self.assertRaises(exception.ValidationError, self.controller.update,
|
||||||
req, fake.VOLUME_ID, 'key1', body=body)
|
req, fake.VOLUME_ID, 'key1', body=body)
|
||||||
|
|
||||||
@ -388,13 +401,15 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
return_create_volume_type_extra_specs)
|
return_create_volume_type_extra_specs)
|
||||||
body = {"key1": "value1"}
|
body = {"key1": "value1"}
|
||||||
|
|
||||||
req = fakes.HTTPRequest.blank(self.api_path + '/bad')
|
req = fakes.HTTPRequest.blank(self.api_path + '/bad',
|
||||||
|
use_admin_context=True)
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
|
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
|
||||||
req, fake.VOLUME_ID, 'bad', body=body)
|
req, fake.VOLUME_ID, 'bad', body=body)
|
||||||
|
|
||||||
def _extra_specs_empty_update(self, body):
|
def _extra_specs_empty_update(self, body):
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s/extra_specs' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s/extra_specs' % (
|
||||||
fake.PROJECT_ID, fake.VOLUME_TYPE_ID))
|
fake.PROJECT_ID, fake.VOLUME_TYPE_ID),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'POST'
|
req.method = 'POST'
|
||||||
|
|
||||||
self.assertRaises(exception.ValidationError,
|
self.assertRaises(exception.ValidationError,
|
||||||
@ -409,7 +424,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
|
|
||||||
def _extra_specs_create_bad_body(self, body):
|
def _extra_specs_create_bad_body(self, body):
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s/extra_specs' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s/extra_specs' % (
|
||||||
fake.PROJECT_ID, fake.VOLUME_TYPE_ID))
|
fake.PROJECT_ID, fake.VOLUME_TYPE_ID),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'POST'
|
req.method = 'POST'
|
||||||
|
|
||||||
self.assertRaises(exception.ValidationError,
|
self.assertRaises(exception.ValidationError,
|
||||||
@ -440,17 +456,20 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
'volume_type_extra_specs_update_or_create',
|
'volume_type_extra_specs_update_or_create',
|
||||||
return_create_volume_type_extra_specs)
|
return_create_volume_type_extra_specs)
|
||||||
body = {"extra_specs": {"key1": "value1"}}
|
body = {"extra_specs": {"key1": "value1"}}
|
||||||
req = fakes.HTTPRequest.blank(self.api_path)
|
req = fakes.HTTPRequest.blank(self.api_path,
|
||||||
|
use_admin_context=True)
|
||||||
with mock.patch.object(
|
with mock.patch.object(
|
||||||
cinder.db,
|
cinder.db,
|
||||||
'volume_get_all',
|
'volume_get_all',
|
||||||
return_value=['a']):
|
return_value=['a']):
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s/extra_specs' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s/extra_specs' % (
|
||||||
fake.PROJECT_ID, fake.VOLUME_TYPE_ID))
|
fake.PROJECT_ID, fake.VOLUME_TYPE_ID),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'POST'
|
req.method = 'POST'
|
||||||
|
|
||||||
body = {"extra_specs": {"key1": "value1"}}
|
body = {"extra_specs": {"key1": "value1"}}
|
||||||
req = fakes.HTTPRequest.blank(self.api_path)
|
req = fakes.HTTPRequest.blank(self.api_path,
|
||||||
|
use_admin_context=True)
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
self.controller.create,
|
self.controller.create,
|
||||||
req,
|
req,
|
||||||
@ -462,7 +481,8 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
|||||||
{'extra_specs': {' ': 'a'}})
|
{'extra_specs': {' ': 'a'}})
|
||||||
def test_create_with_invalid_extra_specs(self, body):
|
def test_create_with_invalid_extra_specs(self, body):
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s/extra_specs' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s/extra_specs' % (
|
||||||
fake.PROJECT_ID, fake.VOLUME_TYPE_ID))
|
fake.PROJECT_ID, fake.VOLUME_TYPE_ID),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'POST'
|
req.method = 'POST'
|
||||||
|
|
||||||
self.assertRaises(exception.ValidationError,
|
self.assertRaises(exception.ValidationError,
|
||||||
|
@ -151,7 +151,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
return_volume_types_destroy)
|
return_volume_types_destroy)
|
||||||
|
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
self.controller._delete(req, DEFAULT_VOLUME_TYPE)
|
self.controller._delete(req, DEFAULT_VOLUME_TYPE)
|
||||||
self.assertEqual(1, len(self.notifier.notifications))
|
self.assertEqual(1, len(self.notifier.notifications))
|
||||||
@ -164,7 +165,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, NOT_FOUND_VOLUME_TYPE))
|
fake.PROJECT_ID, NOT_FOUND_VOLUME_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
self.assertRaises(exception.VolumeTypeNotFound,
|
self.assertRaises(exception.VolumeTypeNotFound,
|
||||||
self.controller._delete, req, NOT_FOUND_VOLUME_TYPE)
|
self.controller._delete, req, NOT_FOUND_VOLUME_TYPE)
|
||||||
self.assertEqual(1, len(self.notifier.notifications))
|
self.assertEqual(1, len(self.notifier.notifications))
|
||||||
@ -175,7 +177,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
self.mock_object(volume_types, 'destroy',
|
self.mock_object(volume_types, 'destroy',
|
||||||
return_volume_types_with_volumes_destroy)
|
return_volume_types_with_volumes_destroy)
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
self.controller._delete(req, DEFAULT_VOLUME_TYPE)
|
self.controller._delete(req, DEFAULT_VOLUME_TYPE)
|
||||||
self.assertEqual(1, len(self.notifier.notifications))
|
self.assertEqual(1, len(self.notifier.notifications))
|
||||||
@ -217,7 +220,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
body = {"volume_type": {"name": "vol_type_1",
|
body = {"volume_type": {"name": "vol_type_1",
|
||||||
"os-volume-type-access:is_public": True,
|
"os-volume-type-access:is_public": True,
|
||||||
"extra_specs": {"key1": "value1"}}}
|
"extra_specs": {"key1": "value1"}}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types' % fake.PROJECT_ID)
|
req = fakes.HTTPRequest.blank('/v3/%s/types' % fake.PROJECT_ID,
|
||||||
|
use_admin_context=True)
|
||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
res_dict = self.controller._create(req, body=body)
|
res_dict = self.controller._create(req, body=body)
|
||||||
@ -242,7 +246,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
body = {"volume_type": {"name": "vol_type_1",
|
body = {"volume_type": {"name": "vol_type_1",
|
||||||
"description": type_description,
|
"description": type_description,
|
||||||
"extra_specs": {"key1": "value1"}}}
|
"extra_specs": {"key1": "value1"}}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types' % fake.PROJECT_ID)
|
req = fakes.HTTPRequest.blank('/v3/%s/types' % fake.PROJECT_ID,
|
||||||
|
use_admin_context=True)
|
||||||
|
|
||||||
res_dict = self.controller._create(req, body=body)
|
res_dict = self.controller._create(req, body=body)
|
||||||
|
|
||||||
@ -274,7 +279,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
|
|
||||||
body = {"volume_type": {"name": "vol_type_1",
|
body = {"volume_type": {"name": "vol_type_1",
|
||||||
"extra_specs": {"key1": "value1"}}}
|
"extra_specs": {"key1": "value1"}}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types' % fake.PROJECT_ID)
|
req = fakes.HTTPRequest.blank('/v3/%s/types' % fake.PROJECT_ID,
|
||||||
|
use_admin_context=True)
|
||||||
self.assertRaises(webob.exc.HTTPConflict,
|
self.assertRaises(webob.exc.HTTPConflict,
|
||||||
self.controller._create, req, body=body)
|
self.controller._create, req, body=body)
|
||||||
|
|
||||||
@ -382,7 +388,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
DEFAULT_VOLUME_TYPE, is_public=False)
|
DEFAULT_VOLUME_TYPE, is_public=False)
|
||||||
body = {"volume_type": {"is_public": False}}
|
body = {"volume_type": {"is_public": False}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
@ -407,7 +414,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
mock_get, mock_update):
|
mock_get, mock_update):
|
||||||
body = {"volume_type": {"is_public": is_public}}
|
body = {"volume_type": {"is_public": is_public}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
|
ctxt = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
|
||||||
req.environ['cinder.context'] = ctxt
|
req.environ['cinder.context'] = ctxt
|
||||||
@ -430,7 +438,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
type_description = ""
|
type_description = ""
|
||||||
body = {"volume_type": {"description": type_description}}
|
body = {"volume_type": {"description": type_description}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
resp = self.controller._update(req, DEFAULT_VOLUME_TYPE, body=body)
|
resp = self.controller._update(req, DEFAULT_VOLUME_TYPE, body=body)
|
||||||
self._check_test_results(resp,
|
self._check_test_results(resp,
|
||||||
@ -466,7 +475,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
body = {"volume_type": {"name": "vol_type_1_1",
|
body = {"volume_type": {"name": "vol_type_1_1",
|
||||||
"description": "vol_type_desc_1_1"}}
|
"description": "vol_type_desc_1_1"}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, NOT_FOUND_VOLUME_TYPE))
|
fake.PROJECT_ID, NOT_FOUND_VOLUME_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
@ -486,7 +496,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
body = {"volume_type": {"name": "vol_type_1_1",
|
body = {"volume_type": {"name": "vol_type_1_1",
|
||||||
"description": "vol_type_desc_1_1"}}
|
"description": "vol_type_desc_1_1"}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
@ -498,7 +509,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
def test_update_no_name_no_description(self):
|
def test_update_no_name_no_description(self):
|
||||||
body = {"volume_type": {}}
|
body = {"volume_type": {}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
|
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
@ -509,7 +521,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
body = {"volume_type": {"name": " ",
|
body = {"volume_type": {"name": " ",
|
||||||
"description": "something"}}
|
"description": "something"}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
|
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
@ -554,7 +567,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
updated_desc = "%s_%s" % (desc, UPDATE_DESC_ONLY_TYPE)
|
updated_desc = "%s_%s" % (desc, UPDATE_DESC_ONLY_TYPE)
|
||||||
body = {"volume_type": {"description": updated_desc}}
|
body = {"volume_type": {"description": updated_desc}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, UPDATE_DESC_ONLY_TYPE))
|
fake.PROJECT_ID, UPDATE_DESC_ONLY_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
@ -577,7 +591,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
updated_desc = "%s_%s" % (desc, DEFAULT_VOLUME_TYPE)
|
updated_desc = "%s_%s" % (desc, DEFAULT_VOLUME_TYPE)
|
||||||
body = {"volume_type": {"is_public": is_public}}
|
body = {"volume_type": {"is_public": is_public}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
@ -614,7 +629,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
# first attempt fail
|
# first attempt fail
|
||||||
body = {"volume_type": {"name": name}}
|
body = {"volume_type": {"name": name}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, UPDATE_NAME_AFTER_DELETE_TYPE))
|
fake.PROJECT_ID, UPDATE_NAME_AFTER_DELETE_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
|
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
@ -629,7 +645,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
self.mock_object(volume_types, 'destroy',
|
self.mock_object(volume_types, 'destroy',
|
||||||
return_volume_types_destroy)
|
return_volume_types_destroy)
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, UPDATE_NAME_AFTER_DELETE_TYPE))
|
fake.PROJECT_ID, UPDATE_NAME_AFTER_DELETE_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
self.assertEqual(0, len(self.notifier.notifications))
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
self.controller._delete(req, UPDATE_NAME_AFTER_DELETE_TYPE)
|
self.controller._delete(req, UPDATE_NAME_AFTER_DELETE_TYPE)
|
||||||
self.assertEqual(1, len(self.notifier.notifications))
|
self.assertEqual(1, len(self.notifier.notifications))
|
||||||
@ -638,7 +655,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
mock_update.side_effect = mock.MagicMock()
|
mock_update.side_effect = mock.MagicMock()
|
||||||
body = {"volume_type": {"name": updated_name}}
|
body = {"volume_type": {"name": updated_name}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, UPDATE_NAME_AFTER_DELETE_TYPE))
|
fake.PROJECT_ID, UPDATE_NAME_AFTER_DELETE_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
|
|
||||||
self.notifier.reset()
|
self.notifier.reset()
|
||||||
@ -702,7 +720,8 @@ class VolumeTypesManageApiTest(test.TestCase):
|
|||||||
def test_update_with_name_null(self):
|
def test_update_with_name_null(self):
|
||||||
body = {"volume_type": {"name": None}}
|
body = {"volume_type": {"name": None}}
|
||||||
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
req = fakes.HTTPRequest.blank('/v3/%s/types/%s' % (
|
||||||
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE))
|
fake.PROJECT_ID, DEFAULT_VOLUME_TYPE),
|
||||||
|
use_admin_context=True)
|
||||||
req.method = 'PUT'
|
req.method = 'PUT'
|
||||||
|
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||||
|
285
cinder/tests/unit/policies/test_type_extra_specs.py
Normal file
285
cinder/tests/unit/policies/test_type_extra_specs.py
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
# Copyright (c) 2021 Red Hat, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 ddt
|
||||||
|
|
||||||
|
from cinder.api.contrib import types_extra_specs
|
||||||
|
from cinder.api import microversions as mv
|
||||||
|
from cinder.api.v3 import types
|
||||||
|
from cinder.policies import type_extra_specs as policy
|
||||||
|
from cinder.policies import volume_type as type_policy
|
||||||
|
from cinder.tests.unit.api import fakes as fake_api
|
||||||
|
from cinder.tests.unit.policies import base
|
||||||
|
from cinder.tests.unit import utils as test_utils
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
|
class TypeExtraSpecsPolicyTest(base.BasePolicyTest):
|
||||||
|
"""Verify extra specs policy settings for the types API"""
|
||||||
|
|
||||||
|
# Deprecated check_str="" allows anyone to read extra specs
|
||||||
|
authorized_readers = [
|
||||||
|
'legacy_admin',
|
||||||
|
'legacy_owner',
|
||||||
|
'system_admin',
|
||||||
|
'project_admin',
|
||||||
|
'project_member',
|
||||||
|
'project_reader',
|
||||||
|
'project_foo',
|
||||||
|
'system_member',
|
||||||
|
'system_reader',
|
||||||
|
'system_foo',
|
||||||
|
'other_project_member',
|
||||||
|
'other_project_reader',
|
||||||
|
]
|
||||||
|
|
||||||
|
unauthorized_readers = [
|
||||||
|
]
|
||||||
|
|
||||||
|
authorized_admins = [
|
||||||
|
'legacy_admin',
|
||||||
|
'system_admin',
|
||||||
|
'project_admin',
|
||||||
|
]
|
||||||
|
|
||||||
|
unauthorized_admins = [
|
||||||
|
'legacy_owner',
|
||||||
|
'system_member',
|
||||||
|
'system_reader',
|
||||||
|
'system_foo',
|
||||||
|
'project_member',
|
||||||
|
'project_reader',
|
||||||
|
'project_foo',
|
||||||
|
'other_project_member',
|
||||||
|
'other_project_reader',
|
||||||
|
]
|
||||||
|
|
||||||
|
unauthorized_exceptions = []
|
||||||
|
|
||||||
|
# Basic policy test is without enforcing scope (which cinder doesn't
|
||||||
|
# yet support) and deprecated rules enabled.
|
||||||
|
def setUp(self, enforce_scope=False, enforce_new_defaults=False,
|
||||||
|
*args, **kwargs):
|
||||||
|
super().setUp(enforce_scope, enforce_new_defaults, *args, **kwargs)
|
||||||
|
self.controller = types_extra_specs.VolumeTypeExtraSpecsController()
|
||||||
|
self.api_path = '/v3/%s/types' % (self.project_id)
|
||||||
|
self.api_version = mv.BASE_VERSION
|
||||||
|
|
||||||
|
@ddt.data(*base.all_users)
|
||||||
|
def test_get_all_policy(self, user_id):
|
||||||
|
vol_type = test_utils.create_volume_type(self.project_admin_context,
|
||||||
|
testcase_instance=self,
|
||||||
|
name='fake_vol_type',
|
||||||
|
extra_specs={'foo': 'bar'})
|
||||||
|
rule_name = policy.GET_ALL_POLICY
|
||||||
|
url = '%s/%s/extra_specs' % (self.api_path, vol_type.id)
|
||||||
|
req = fake_api.HTTPRequest.blank(url, version=self.api_version)
|
||||||
|
self.common_policy_check(user_id,
|
||||||
|
self.authorized_readers,
|
||||||
|
self.unauthorized_readers,
|
||||||
|
self.unauthorized_exceptions,
|
||||||
|
rule_name,
|
||||||
|
self.controller.index,
|
||||||
|
req,
|
||||||
|
type_id=vol_type.id)
|
||||||
|
|
||||||
|
@ddt.data(*base.all_users)
|
||||||
|
def test_get_policy(self, user_id):
|
||||||
|
vol_type = test_utils.create_volume_type(self.project_admin_context,
|
||||||
|
testcase_instance=self,
|
||||||
|
name='fake_vol_type',
|
||||||
|
extra_specs={'foo': 'bar'})
|
||||||
|
rule_name = policy.GET_POLICY
|
||||||
|
url = '%s/%s/extra_specs/foo' % (self.api_path, vol_type.id)
|
||||||
|
req = fake_api.HTTPRequest.blank(url, version=self.api_version)
|
||||||
|
|
||||||
|
# Relax the READ_SENSITIVE_POLICY policy so that any user is able
|
||||||
|
# to "see" the spec.
|
||||||
|
self.policy.set_rules({policy.READ_SENSITIVE_POLICY: ""},
|
||||||
|
overwrite=False)
|
||||||
|
|
||||||
|
self.common_policy_check(user_id,
|
||||||
|
self.authorized_readers,
|
||||||
|
self.unauthorized_readers,
|
||||||
|
self.unauthorized_exceptions,
|
||||||
|
rule_name,
|
||||||
|
self.controller.show,
|
||||||
|
req,
|
||||||
|
type_id=vol_type.id,
|
||||||
|
id='foo')
|
||||||
|
|
||||||
|
@ddt.data(*base.all_users)
|
||||||
|
def test_create_policy(self, user_id):
|
||||||
|
vol_type = test_utils.create_volume_type(self.project_admin_context,
|
||||||
|
testcase_instance=self,
|
||||||
|
name='fake_vol_type')
|
||||||
|
rule_name = policy.CREATE_POLICY
|
||||||
|
url = '%s/%s/extra_specs' % (self.api_path, vol_type.id)
|
||||||
|
req = fake_api.HTTPRequest.blank(url, version=self.api_version)
|
||||||
|
req.method = 'POST'
|
||||||
|
body = {
|
||||||
|
"extra_specs": {
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.common_policy_check(user_id,
|
||||||
|
self.authorized_admins,
|
||||||
|
self.unauthorized_admins,
|
||||||
|
self.unauthorized_exceptions,
|
||||||
|
rule_name,
|
||||||
|
self.controller.create,
|
||||||
|
req,
|
||||||
|
type_id=vol_type.id,
|
||||||
|
body=body)
|
||||||
|
|
||||||
|
@ddt.data(*base.all_users)
|
||||||
|
def test_update_policy(self, user_id):
|
||||||
|
vol_type = test_utils.create_volume_type(self.project_admin_context,
|
||||||
|
testcase_instance=self,
|
||||||
|
name='fake_vol_type',
|
||||||
|
extra_specs={'foo': 'bar'})
|
||||||
|
rule_name = policy.UPDATE_POLICY
|
||||||
|
url = '%s/%s/extra_specs/foo' % (self.api_path, vol_type.id)
|
||||||
|
req = fake_api.HTTPRequest.blank(url, version=self.api_version)
|
||||||
|
req.method = 'PUT'
|
||||||
|
body = {"foo": "zap"}
|
||||||
|
|
||||||
|
self.common_policy_check(user_id,
|
||||||
|
self.authorized_admins,
|
||||||
|
self.unauthorized_admins,
|
||||||
|
self.unauthorized_exceptions,
|
||||||
|
rule_name,
|
||||||
|
self.controller.update,
|
||||||
|
req,
|
||||||
|
type_id=vol_type.id,
|
||||||
|
id='foo',
|
||||||
|
body=body)
|
||||||
|
|
||||||
|
@ddt.data(*base.all_users)
|
||||||
|
def test_delete_policy(self, user_id):
|
||||||
|
vol_type = test_utils.create_volume_type(self.project_admin_context,
|
||||||
|
testcase_instance=self,
|
||||||
|
name='fake_vol_type',
|
||||||
|
extra_specs={'foo': 'bar'})
|
||||||
|
rule_name = policy.DELETE_POLICY
|
||||||
|
url = '%s/%s/extra_specs/foo' % (self.api_path, vol_type.id)
|
||||||
|
req = fake_api.HTTPRequest.blank(url, version=self.api_version)
|
||||||
|
req.method = 'DELETE'
|
||||||
|
|
||||||
|
self.common_policy_check(user_id,
|
||||||
|
self.authorized_admins,
|
||||||
|
self.unauthorized_admins,
|
||||||
|
self.unauthorized_exceptions,
|
||||||
|
rule_name,
|
||||||
|
self.controller.delete,
|
||||||
|
req,
|
||||||
|
type_id=vol_type.id,
|
||||||
|
id='foo')
|
||||||
|
|
||||||
|
@ddt.data(*base.all_users)
|
||||||
|
def test_read_sensitive_policy(self, user_id):
|
||||||
|
# The 'multiattach' extra spec is user visible, and the
|
||||||
|
# 'sensitive' extra spec should not be user visible.
|
||||||
|
extra_specs = {
|
||||||
|
'multiattach': '<is> True',
|
||||||
|
'sensitive': 'secret',
|
||||||
|
}
|
||||||
|
vol_type = test_utils.create_volume_type(self.project_admin_context,
|
||||||
|
testcase_instance=self,
|
||||||
|
name='fake_vol_type',
|
||||||
|
extra_specs=extra_specs)
|
||||||
|
rule_name = policy.READ_SENSITIVE_POLICY
|
||||||
|
url = '%s/%s' % (self.api_path, vol_type.id)
|
||||||
|
req = fake_api.HTTPRequest.blank(url, version=self.api_version)
|
||||||
|
|
||||||
|
# Relax these policies in order to get past those checks.
|
||||||
|
self.policy.set_rules({type_policy.GET_POLICY: ""},
|
||||||
|
overwrite=False)
|
||||||
|
self.policy.set_rules({type_policy.EXTRA_SPEC_POLICY: ""},
|
||||||
|
overwrite=False)
|
||||||
|
|
||||||
|
# With the relaxed policies, all users are authorized because
|
||||||
|
# failing the READ_SENSITIVE_POLICY policy check is not fatal.
|
||||||
|
authorized_users = [user_id]
|
||||||
|
unauthorized_users = []
|
||||||
|
|
||||||
|
controller = types.VolumeTypesController()
|
||||||
|
response = self.common_policy_check(user_id,
|
||||||
|
authorized_users,
|
||||||
|
unauthorized_users,
|
||||||
|
self.unauthorized_exceptions,
|
||||||
|
rule_name,
|
||||||
|
controller.show,
|
||||||
|
req,
|
||||||
|
id=vol_type.id)
|
||||||
|
|
||||||
|
if user_id in self.authorized_admins:
|
||||||
|
# Admins should see all extra specs
|
||||||
|
expected = extra_specs
|
||||||
|
else:
|
||||||
|
# Non-admins should only see user visible extra specs
|
||||||
|
expected = {'multiattach': '<is> True'}
|
||||||
|
self.assertDictEqual(expected, response['volume_type']['extra_specs'])
|
||||||
|
|
||||||
|
|
||||||
|
class TypeExtraSpecsPolicySecureRbacTest(TypeExtraSpecsPolicyTest):
|
||||||
|
authorized_readers = [
|
||||||
|
'legacy_admin',
|
||||||
|
'system_admin',
|
||||||
|
'project_admin',
|
||||||
|
'project_member',
|
||||||
|
'project_reader',
|
||||||
|
'system_member',
|
||||||
|
'system_reader',
|
||||||
|
'other_project_member',
|
||||||
|
'other_project_reader',
|
||||||
|
]
|
||||||
|
|
||||||
|
unauthorized_readers = [
|
||||||
|
# These are unauthorized because they don't have the reader role
|
||||||
|
'legacy_owner',
|
||||||
|
'project_foo',
|
||||||
|
'system_foo',
|
||||||
|
]
|
||||||
|
|
||||||
|
# NOTE(Xena): The authorized_admins and unauthorized_admins are the same
|
||||||
|
# as the TypeExtraSpecsPolicyTest. This is because in Xena the "admin only"
|
||||||
|
# rules are the legacy RULE_ADMIN_API. This will change in Yoga, when
|
||||||
|
# RULE_ADMIN_API will be deprecated in favor of the SYSTEM_ADMIN rule that
|
||||||
|
# is scope based.
|
||||||
|
authorized_admins = [
|
||||||
|
'legacy_admin',
|
||||||
|
'system_admin',
|
||||||
|
'project_admin',
|
||||||
|
]
|
||||||
|
|
||||||
|
unauthorized_admins = [
|
||||||
|
'legacy_owner',
|
||||||
|
'system_member',
|
||||||
|
'system_reader',
|
||||||
|
'system_foo',
|
||||||
|
'project_member',
|
||||||
|
'project_reader',
|
||||||
|
'project_foo',
|
||||||
|
'other_project_member',
|
||||||
|
'other_project_reader',
|
||||||
|
]
|
||||||
|
|
||||||
|
def setUp(self, *args, **kwargs):
|
||||||
|
# Test secure RBAC by disabling deprecated policy rules (scope
|
||||||
|
# is still not enabled).
|
||||||
|
super().setUp(enforce_scope=False, enforce_new_defaults=True,
|
||||||
|
*args, **kwargs)
|
@ -25,6 +25,7 @@ from cinder.tests.unit.api import fakes as fake_api
|
|||||||
from cinder.tests.unit import fake_constants
|
from cinder.tests.unit import fake_constants
|
||||||
from cinder.tests.unit.policies import base
|
from cinder.tests.unit.policies import base
|
||||||
from cinder.tests.unit.policies import test_base
|
from cinder.tests.unit.policies import test_base
|
||||||
|
from cinder.tests.unit import utils as test_utils
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
@ -75,9 +76,11 @@ class VolumeTypePolicyTest(base.BasePolicyTest):
|
|||||||
|
|
||||||
@ddt.data(*base.all_users)
|
@ddt.data(*base.all_users)
|
||||||
def test_type_get_policy(self, user_id):
|
def test_type_get_policy(self, user_id):
|
||||||
|
vol_type = test_utils.create_volume_type(self.project_admin_context,
|
||||||
|
testcase_instance=self,
|
||||||
|
name='fake_vol_type')
|
||||||
rule_name = type_policy.GET_POLICY
|
rule_name = type_policy.GET_POLICY
|
||||||
# the default type is guaranteed to exist
|
url = '%s/%s' % (self.api_path, vol_type.id)
|
||||||
url = self.api_path + '/default'
|
|
||||||
req = fake_api.HTTPRequest.blank(url, version=self.api_version)
|
req = fake_api.HTTPRequest.blank(url, version=self.api_version)
|
||||||
self.common_policy_check(user_id,
|
self.common_policy_check(user_id,
|
||||||
self.authorized_readers,
|
self.authorized_readers,
|
||||||
@ -86,7 +89,48 @@ class VolumeTypePolicyTest(base.BasePolicyTest):
|
|||||||
rule_name,
|
rule_name,
|
||||||
self.controller.show,
|
self.controller.show,
|
||||||
req,
|
req,
|
||||||
'default')
|
id=vol_type.id)
|
||||||
|
|
||||||
|
@ddt.data(*base.all_users)
|
||||||
|
def test_extra_spec_policy(self, user_id):
|
||||||
|
vol_type = test_utils.create_volume_type(
|
||||||
|
self.project_admin_context,
|
||||||
|
testcase_instance=self,
|
||||||
|
name='fake_vol_type',
|
||||||
|
extra_specs={'multiattach': '<is> True'})
|
||||||
|
rule_name = type_policy.EXTRA_SPEC_POLICY
|
||||||
|
url = '%s/%s' % (self.api_path, vol_type.id)
|
||||||
|
req = fake_api.HTTPRequest.blank(url, version=self.api_version)
|
||||||
|
|
||||||
|
# Relax the GET_POLICY in order to get past that check.
|
||||||
|
self.policy.set_rules({type_policy.GET_POLICY: ""},
|
||||||
|
overwrite=False)
|
||||||
|
|
||||||
|
# With the relaxed GET_POLICY, all users are authorized because
|
||||||
|
# failing the policy check is not fatal.
|
||||||
|
authorized_readers = [user_id]
|
||||||
|
unauthorized_readers = []
|
||||||
|
|
||||||
|
response = self.common_policy_check(user_id,
|
||||||
|
authorized_readers,
|
||||||
|
unauthorized_readers,
|
||||||
|
self.unauthorized_exceptions,
|
||||||
|
rule_name,
|
||||||
|
self.controller.show,
|
||||||
|
req,
|
||||||
|
id=vol_type.id)
|
||||||
|
|
||||||
|
# Check whether the response should contain extra_specs. The logic
|
||||||
|
# is a little unusual:
|
||||||
|
# - The new rule is SYSTEM_READER_OR_PROJECT_READER (i.e. users
|
||||||
|
# with the 'reader' role)
|
||||||
|
# - The deprecated rule is RULE_ADMIN_API (i.e. users with the
|
||||||
|
# 'admin' role)
|
||||||
|
context = self.create_context(user_id)
|
||||||
|
if 'reader' in context.roles or 'admin' in context.roles:
|
||||||
|
self.assertIn('extra_specs', response['volume_type'])
|
||||||
|
else:
|
||||||
|
self.assertNotIn('extra_specs', response['volume_type'])
|
||||||
|
|
||||||
|
|
||||||
class VolumeTypePolicySecureRbacTest(VolumeTypePolicyTest):
|
class VolumeTypePolicySecureRbacTest(VolumeTypePolicyTest):
|
||||||
|
@ -16,30 +16,3 @@
|
|||||||
# enable, set-log and get-log actions.
|
# enable, set-log and get-log actions.
|
||||||
# PUT /os-services/{action}
|
# PUT /os-services/{action}
|
||||||
#"volume_extension:services:update": "rule:admin_api"
|
#"volume_extension:services:update": "rule:admin_api"
|
||||||
|
|
||||||
# Create, update and delete volume type.
|
|
||||||
# POST /types
|
|
||||||
# PUT /types
|
|
||||||
# DELETE /types
|
|
||||||
"volume_extension:types_manage": ""
|
|
||||||
|
|
||||||
# List type extra specs.
|
|
||||||
# GET /types/{type_id}/extra_specs
|
|
||||||
"volume_extension:types_extra_specs:index": ""
|
|
||||||
|
|
||||||
# Create type extra specs.
|
|
||||||
# POST /types/{type_id}/extra_specs
|
|
||||||
"volume_extension:types_extra_specs:create": ""
|
|
||||||
|
|
||||||
# Show one specified type extra specs.
|
|
||||||
# GET /types/{type_id}/extra_specs/{extra_spec_key}
|
|
||||||
"volume_extension:types_extra_specs:show": ""
|
|
||||||
|
|
||||||
# Update type extra specs.
|
|
||||||
# PUT /types/{type_id}/extra_specs/{extra_spec_key}
|
|
||||||
"volume_extension:types_extra_specs:update": ""
|
|
||||||
|
|
||||||
# Delete type extra specs.
|
|
||||||
# DELETE /types/{type_id}/extra_specs/{extra_spec_key}
|
|
||||||
"volume_extension:types_extra_specs:delete": ""
|
|
||||||
|
|
||||||
|
@ -1693,13 +1693,13 @@ matrix is validated by human beings.
|
|||||||
| ``GET /types``
|
| ``GET /types``
|
||||||
| The ability to make these API calls is governed by other policies.
|
| The ability to make these API calls is governed by other policies.
|
||||||
- volume_extension:access_types_extra_specs
|
- volume_extension:access_types_extra_specs
|
||||||
- rule:admin_api
|
- empty
|
||||||
- no
|
- yes
|
||||||
- no
|
- yes
|
||||||
- no
|
- yes
|
||||||
- no
|
- yes
|
||||||
|
- yes
|
||||||
- yes
|
- yes
|
||||||
- no
|
|
||||||
- yes
|
- yes
|
||||||
* - List or show volume type with access type qos specs id attribute
|
* - List or show volume type with access type qos specs id attribute
|
||||||
- | Adds ``qos_specs_id`` to the following responses:
|
- | Adds ``qos_specs_id`` to the following responses:
|
||||||
@ -2237,13 +2237,13 @@ matrix is validated by human beings.
|
|||||||
* - List type extra specs
|
* - List type extra specs
|
||||||
- ``GET /types/{type_id}/extra_specs``
|
- ``GET /types/{type_id}/extra_specs``
|
||||||
- volume_extension:types_extra_specs:index
|
- volume_extension:types_extra_specs:index
|
||||||
- rule:admin_api
|
- empty
|
||||||
- no
|
- yes
|
||||||
- no
|
- yes
|
||||||
- no
|
- yes
|
||||||
- no
|
- yes
|
||||||
|
- yes
|
||||||
- yes
|
- yes
|
||||||
- no
|
|
||||||
- yes
|
- yes
|
||||||
* - Create type extra specs
|
* - Create type extra specs
|
||||||
- ``POST /types/{type_id}/extra_specs``
|
- ``POST /types/{type_id}/extra_specs``
|
||||||
@ -2259,13 +2259,13 @@ matrix is validated by human beings.
|
|||||||
* - Show one specified type extra specs
|
* - Show one specified type extra specs
|
||||||
- ``GET /types/{type_id}/extra_specs/{extra_spec_key}``
|
- ``GET /types/{type_id}/extra_specs/{extra_spec_key}``
|
||||||
- volume_extension:types_extra_specs:show
|
- volume_extension:types_extra_specs:show
|
||||||
- rule:admin_api
|
- empty
|
||||||
- no
|
- yes
|
||||||
- no
|
- yes
|
||||||
- no
|
- yes
|
||||||
- no
|
- yes
|
||||||
|
- yes
|
||||||
- yes
|
- yes
|
||||||
- no
|
|
||||||
- yes
|
- yes
|
||||||
* - Update type extra specs
|
* - Update type extra specs
|
||||||
- ``PUT /types/{type_id}/extra_specs/{extra_spec_key}``
|
- ``PUT /types/{type_id}/extra_specs/{extra_spec_key}``
|
||||||
@ -2289,6 +2289,23 @@ matrix is validated by human beings.
|
|||||||
- yes
|
- yes
|
||||||
- no
|
- no
|
||||||
- yes
|
- yes
|
||||||
|
* - Include extra_specs fields that may reveal sensitive information about
|
||||||
|
the deployment that should not be exposed to end users in various
|
||||||
|
volume-type responses that show extra_specs.
|
||||||
|
- | ``GET /types``
|
||||||
|
| ``GET /types/{type_id}``
|
||||||
|
| ``GET /types/{type_id}/extra_specs``
|
||||||
|
| ``GET /types/{type_id}/extra_specs/{extra_spec_key}``
|
||||||
|
| The ability to make these API calls is governed by other policies.
|
||||||
|
- volume_extension:types_extra_specs:read_sensitive
|
||||||
|
- rule:admin_api
|
||||||
|
- no
|
||||||
|
- no
|
||||||
|
- no
|
||||||
|
- no
|
||||||
|
- yes
|
||||||
|
- no
|
||||||
|
- yes
|
||||||
|
|
||||||
.. list-table:: Volumes
|
.. list-table:: Volumes
|
||||||
:header-rows: 1
|
:header-rows: 1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user