| 19 | 19 |
class RbacV3VolumeTypesTests(rbac_base.VolumeV3RbacBaseTests):
|
| 20 | 20 |
|
| 21 | 21 |
min_microversion = '3.3'
|
| 22 | |
extra_spec_key = 'key1'
|
|
22 |
extra_specs = {
|
|
23 |
'key1': 'value1',
|
|
24 |
'multiattach': '<is> False',
|
|
25 |
'volume_backend_name': 'test-backend-name',
|
|
26 |
'RESKEY:availability_zones': 'test-az',
|
|
27 |
'replication_enabled': '<is> False'
|
|
28 |
}
|
|
29 |
extra_specs_keys = list(extra_specs.keys())
|
|
30 |
expected_extra_specs = {
|
|
31 |
"reader": [
|
|
32 |
'multiattach', 'RESKEY:availability_zones', 'replication_enabled'
|
|
33 |
],
|
|
34 |
"member": [
|
|
35 |
'multiattach', 'RESKEY:availability_zones', 'replication_enabled'
|
|
36 |
],
|
|
37 |
"admin": extra_specs_keys
|
|
38 |
}
|
|
39 |
|
| 23 | 40 |
encryption_type_key_cipher = 'cipher'
|
| 24 | 41 |
create_kwargs = {
|
| 25 | 42 |
'provider': 'LuksEncryptor',
|
|
| 51 | 68 |
# create a volume type
|
| 52 | 69 |
if not name:
|
| 53 | 70 |
name = data_utils.rand_name("volume-type")
|
| 54 | |
extra_specs = {cls.extra_spec_key: 'value1'}
|
| 55 | 71 |
params = {'name': name,
|
| 56 | 72 |
'description': "description",
|
| 57 | |
'extra_specs': extra_specs,
|
|
73 |
'extra_specs': cls.extra_specs,
|
| 58 | 74 |
'os-volume-type-access:is_public': True}
|
| 59 | 75 |
volume_type = cls.admin_types_client.create_volume_type(
|
| 60 | 76 |
**params
|
|
| 65 | 81 |
cls.encryption_type = \
|
| 66 | 82 |
cls.admin_encryption_types_client.create_encryption_type(
|
| 67 | 83 |
volume_type['id'], **cls.create_kwargs)['encryption']
|
|
84 |
# NOTE: strictly speaking, this is NOT a volume_type field;
|
|
85 |
# we save it for convenience in these tests
|
|
86 |
volume_type['encryption_id'] = cls.encryption_type['encryption_id']
|
| 68 | 87 |
|
| 69 | 88 |
if cleanup:
|
| 70 | 89 |
cls.addClassResourceCleanup(
|
|
| 72 | 91 |
)
|
| 73 | 92 |
|
| 74 | 93 |
return volume_type
|
|
94 |
|
|
95 |
def _extra_specs_content_validator(self, client, extra_specs):
|
|
96 |
"""Validation of volume type's extra specs content
|
|
97 |
|
|
98 |
Addition for feature:
|
|
99 |
https://specs.openstack.org/openstack/cinder-specs/specs/xena/
|
|
100 |
expose-cinder-user-visible-extra-specs-spec.html
|
|
101 |
|
|
102 |
This feature allows 'readers' and 'members' to "see" volume type's
|
|
103 |
extra specs:
|
|
104 |
'multiattach', 'RESKEY:availability_zones' and 'replication_enabled'
|
|
105 |
|
|
106 |
Args:
|
|
107 |
client: Client object to be used
|
|
108 |
extra_specs: extra_specs dict from response
|
|
109 |
|
|
110 |
Returns:
|
|
111 |
Boolean: True if lists are equal, false otherwise
|
|
112 |
"""
|
|
113 |
role = client.user.split('-')[-1]
|
|
114 |
return (sorted(list(extra_specs.keys())) ==
|
|
115 |
sorted(self.expected_extra_specs[role]))
|
| 75 | 116 |
|
| 76 | 117 |
def _update_volume_type(self, expected_status):
|
| 77 | 118 |
"""Update volume type"""
|
|
| 111 | 152 |
expected_status=expected_status,
|
| 112 | 153 |
volume_type_id=self.volume_type['id']
|
| 113 | 154 |
)['extra_specs']
|
| 114 | |
self.assertIn(
|
| 115 | |
self.extra_spec_key,
|
| 116 | |
list(extra_specs.keys()),
|
| 117 | |
message=f"Key '{self.extra_spec_key}' not found in extra_specs."
|
|
155 |
self.assertTrue(
|
|
156 |
self._extra_specs_content_validator(
|
|
157 |
client=self.client, extra_specs=extra_specs
|
|
158 |
)
|
| 118 | 159 |
)
|
| 119 | 160 |
|
| 120 | 161 |
def _show_extra_spec_for_volume_type(self, expected_status):
|
| 121 | 162 |
"""Show extra_spec for a volume type"""
|
| 122 | |
self.do_request(
|
|
163 |
|
|
164 |
# Using 'multiattach' extra spec because all admin, member and readers
|
|
165 |
# should be able to "see".
|
|
166 |
spec = self.do_request(
|
| 123 | 167 |
method='show_volume_type_extra_specs',
|
| 124 | 168 |
expected_status=expected_status,
|
| 125 | 169 |
volume_type_id=self.volume_type['id'],
|
| 126 | |
extra_specs_name=self.extra_spec_key
|
|
170 |
extra_specs_name='multiattach'
|
|
171 |
)
|
|
172 |
self.assertEqual(spec['multiattach'], self.extra_specs['multiattach'])
|
|
173 |
|
|
174 |
# Using 'volume_backend_name' extra spec because only admin should
|
|
175 |
# "see" it.
|
|
176 |
role = self.client.user.split('-')[-1]
|
|
177 |
# 'reader' and 'member' will get 404 (NotFound) if they try to show
|
|
178 |
# the extra spec 'volume_backend_name'
|
|
179 |
try:
|
|
180 |
spec = self.do_request(
|
|
181 |
method='show_volume_type_extra_specs',
|
|
182 |
expected_status=expected_status,
|
|
183 |
volume_type_id=self.volume_type['id'],
|
|
184 |
extra_specs_name='volume_backend_name'
|
|
185 |
)
|
|
186 |
except exceptions.NotFound:
|
|
187 |
# NotFound exception should be thrown for
|
|
188 |
# 'reader' and 'member' only
|
|
189 |
self.assertNotEqual(
|
|
190 |
role, 'admin',
|
|
191 |
"NotFound exception was thrown for admin"
|
|
192 |
)
|
|
193 |
return
|
|
194 |
|
|
195 |
# If no exception thrown, then check the content
|
|
196 |
# Only admin should reach to this point
|
|
197 |
self.assertNotIn(
|
|
198 |
role, ['reader', 'member'],
|
|
199 |
"NotFound should be thrown for non admin role"
|
|
200 |
)
|
|
201 |
self.assertEqual(
|
|
202 |
spec['volume_backend_name'],
|
|
203 |
self.extra_specs['volume_backend_name']
|
| 127 | 204 |
)
|
| 128 | 205 |
|
| 129 | 206 |
def _update_extra_spec_for_volume_type(self, expected_status):
|
| 130 | 207 |
"""Update extra_spec for a volume type"""
|
| 131 | |
spec_name = self.extra_spec_key
|
| 132 | |
extra_spec = {spec_name: 'updated value'}
|
|
208 |
extra_spec = {'key1': 'key1 updated value'}
|
| 133 | 209 |
self.do_request(
|
| 134 | 210 |
method='update_volume_type_extra_specs',
|
| 135 | 211 |
expected_status=expected_status,
|
| 136 | 212 |
volume_type_id=self.volume_type['id'],
|
| 137 | |
extra_spec_name=spec_name,
|
|
213 |
extra_spec_name='key1',
|
| 138 | 214 |
extra_specs=extra_spec
|
| 139 | 215 |
)
|
| 140 | 216 |
|
|
| 146 | 222 |
method='delete_volume_type_extra_specs',
|
| 147 | 223 |
expected_status=expected_status,
|
| 148 | 224 |
volume_type_id=volume_type['id'],
|
| 149 | |
extra_spec_name=self.extra_spec_key
|
|
225 |
extra_spec_name='key1'
|
| 150 | 226 |
)
|
| 151 | 227 |
|
| 152 | 228 |
def _show_volume_type_detail(self, expected_status):
|
| 153 | 229 |
"""Show volume type"""
|
| 154 | |
self.do_request(
|
|
230 |
details = self.do_request(
|
| 155 | 231 |
method='show_volume_type',
|
| 156 | 232 |
expected_status=expected_status,
|
| 157 | 233 |
volume_type_id=self.volume_type['id']
|
|
234 |
)['volume_type']
|
|
235 |
self.assertTrue(
|
|
236 |
self._extra_specs_content_validator(
|
|
237 |
client=self.client, extra_specs=details['extra_specs']
|
|
238 |
)
|
| 158 | 239 |
)
|
| 159 | 240 |
|
| 160 | 241 |
def _show_default_volume_type(self, expected_status):
|
|
| 180 | 261 |
|
| 181 | 262 |
def _list_volume_types(self, expected_status):
|
| 182 | 263 |
"""List all volume types"""
|
| 183 | |
self.do_request(
|
|
264 |
volume_types = self.do_request(
|
| 184 | 265 |
method='list_volume_types',
|
| 185 | 266 |
expected_status=expected_status
|
| 186 | |
)
|
|
267 |
)['volume_types']
|
|
268 |
for volume_type in volume_types:
|
|
269 |
if volume_type['id'] == self.volume_type['id']:
|
|
270 |
self._extra_specs_content_validator(
|
|
271 |
client=self.client, extra_specs=volume_type['extra_specs']
|
|
272 |
)
|
| 187 | 273 |
|
| 188 | 274 |
def _create_volume_type(self, expected_status):
|
| 189 | 275 |
"""Create a volume type"""
|
|
| 225 | 311 |
method='delete_encryption_type',
|
| 226 | 312 |
expected_status=expected_status,
|
| 227 | 313 |
client=self.encryption_types_client,
|
| 228 | |
volume_type_id=volume_type['id']
|
|
314 |
volume_type_id=volume_type['id'],
|
|
315 |
encryption_id=volume_type['encryption_id']
|
| 229 | 316 |
)
|
| 230 | 317 |
|
| 231 | 318 |
def _create_encryption_type(self, expected_status):
|
|
| 249 | 336 |
expected_status=expected_status,
|
| 250 | 337 |
client=self.encryption_types_client,
|
| 251 | 338 |
volume_type_id=self.volume_type['id'],
|
|
339 |
encryption_id=self.volume_type['encryption_id'],
|
| 252 | 340 |
**update_kwargs
|
| 253 | 341 |
)
|
| 254 | 342 |
|
|
| 274 | 362 |
expected_status=exceptions.Forbidden
|
| 275 | 363 |
)
|
| 276 | 364 |
|
| 277 | |
@decorators.skip_because(bug='2018467')
|
| 278 | 365 |
@decorators.idempotent_id('9499752c-3b27-41a3-8f55-4bdba7297f92')
|
| 279 | 366 |
def test_list_all_extra_specs_for_volume_type(self):
|
| 280 | 367 |
self._list_all_extra_specs_for_volume_type(
|
| 281 | 368 |
expected_status=200
|
| 282 | 369 |
)
|
| 283 | 370 |
|
| 284 | |
@decorators.skip_because(bug='2018467')
|
| 285 | 371 |
@decorators.idempotent_id('a38f7248-3a5b-4e51-8e32-d2dcf9c771ea')
|
| 286 | 372 |
def test_show_extra_spec_for_volume_type(self):
|
| 287 | 373 |
self._show_extra_spec_for_volume_type(expected_status=200)
|
|
| 363 | 449 |
expected_status=exceptions.Forbidden
|
| 364 | 450 |
)
|
| 365 | 451 |
|
| 366 | |
@decorators.skip_because(bug='2018467')
|
| 367 | 452 |
@decorators.idempotent_id('82fd0d34-17b3-4f45-bd2e-728c9a8bff8c')
|
| 368 | 453 |
def test_list_all_extra_specs_for_volume_type(self):
|
| 369 | 454 |
self._list_all_extra_specs_for_volume_type(
|
| 370 | 455 |
expected_status=200
|
| 371 | 456 |
)
|
| 372 | 457 |
|
| 373 | |
@decorators.skip_because(bug='2018467')
|
| 374 | 458 |
@decorators.idempotent_id('67aa0b40-7c0a-4ae7-8682-fb4f20abd390')
|
| 375 | 459 |
def test_show_extra_spec_for_volume_type(self):
|
| 376 | 460 |
self._show_extra_spec_for_volume_type(expected_status=200)
|
|
| 456 | 540 |
expected_status=200
|
| 457 | 541 |
)
|
| 458 | 542 |
|
| 459 | |
@decorators.skip_because(bug='2018467')
|
| 460 | 543 |
@decorators.idempotent_id('a2cca7b6-0af9-47e5-b8c1-4e0f01822d4e')
|
| 461 | 544 |
def test_show_extra_spec_for_volume_type(self):
|
| 462 | 545 |
self._show_extra_spec_for_volume_type(expected_status=200)
|