Coverage for manila/tests/api/v1/test_share_metadata.py: 99%
300 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright 2011 OpenStack Foundation
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16import ddt
17from oslo_config import cfg
18from oslo_serialization import jsonutils
19import webob
21from manila.api.v1 import share_metadata
22from manila.api.v1 import shares
23from manila.common import constants
24from manila import context
25from manila import db
26from manila.share import api
27from manila import test
28from manila.tests.api import fakes
30CONF = cfg.CONF
31AFFINITY_KEY = constants.AdminOnlyMetadata.AFFINITY_KEY
32ANTI_AFFINITY_KEY = constants.AdminOnlyMetadata.ANTI_AFFINITY_KEY
35@ddt.ddt
36class ShareMetaDataTest(test.TestCase):
38 def setUp(self):
39 super(ShareMetaDataTest, self).setUp()
40 self.share_api = api.API()
41 self.share_controller = shares.ShareController()
42 self.controller = share_metadata.ShareMetadataController()
43 self.ctxt = context.RequestContext('admin', 'fake', True)
44 self.origin_metadata = {
45 "key1": "value1",
46 "key2": "value2",
47 "key3": "value3",
48 }
49 self.share = db.share_create(self.ctxt, {})
50 self.share_id = self.share['id']
51 self.url = '/shares/%s/metadata' % self.share_id
52 db.share_metadata_update(
53 self.ctxt, self.share_id, self.origin_metadata, delete=False)
55 def test_index(self):
56 req = fakes.HTTPRequest.blank(self.url)
57 res_dict = self.controller.index(req, self.share_id)
59 expected = {
60 'metadata': {
61 'key1': 'value1',
62 'key2': 'value2',
63 'key3': 'value3',
64 },
65 }
67 self.assertEqual(expected, res_dict)
69 def test_index_nonexistent_share(self):
70 req = fakes.HTTPRequest.blank(self.url)
71 self.assertRaises(webob.exc.HTTPNotFound,
72 self.controller.index, req, self.url)
74 def test_index_no_data(self):
75 db.share_metadata_update(
76 self.ctxt, self.share_id, {}, delete=True)
77 req = fakes.HTTPRequest.blank(self.url)
78 res_dict = self.controller.index(req, self.share_id)
79 expected = {'metadata': {}}
80 self.assertEqual(expected, res_dict)
82 def test_show(self):
83 req = fakes.HTTPRequest.blank(self.url + '/key2')
85 res_dict = self.controller.show(req, self.share_id, 'key2')
87 expected = {'meta': {'key2': 'value2'}}
88 self.assertEqual(expected, res_dict)
90 def test_show_nonexistent_share(self):
91 req = fakes.HTTPRequest.blank(self.url + '/key2')
92 self.assertRaises(
93 webob.exc.HTTPNotFound,
94 self.controller.show,
95 req, "nonexistent_share", 'key2')
97 def test_show_meta_not_found(self):
98 req = fakes.HTTPRequest.blank(self.url + '/key6')
99 self.assertRaises(webob.exc.HTTPNotFound,
100 self.controller.show, req, self.share_id, 'key6')
102 def test_delete(self):
103 req = fakes.HTTPRequest.blank(self.url + '/key2')
104 req.method = 'DELETE'
105 res = self.controller.delete(req, self.share_id, 'key2')
107 self.assertEqual(200, res.status_int)
109 def test_delete_nonexistent_share(self):
110 req = fakes.HTTPRequest.blank(self.url + '/key1')
111 req.method = 'DELETE'
112 self.assertRaises(
113 webob.exc.HTTPNotFound,
114 self.controller.delete,
115 req, "nonexistent_share", 'key1')
117 def test_delete_meta_not_found(self):
118 req = fakes.HTTPRequest.blank(self.url + '/key6')
119 req.method = 'DELETE'
120 self.assertRaises(webob.exc.HTTPNotFound,
121 self.controller.delete, req, self.share_id, 'key6')
123 @ddt.data((AFFINITY_KEY, '/' + AFFINITY_KEY),
124 (ANTI_AFFINITY_KEY, '/' + ANTI_AFFINITY_KEY))
125 @ddt.unpack
126 def test_delete_affinities_user(self, key, path):
127 self.userctxt = context.RequestContext('demo', 'fake', False)
128 req = fakes.HTTPRequest.blank(self.url + path)
129 req.method = 'DELETE'
130 req.content_type = "application/json"
131 req.environ['manila.context'] = self.userctxt
132 establish = {key: 'share1'}
133 db.share_metadata_update(
134 self.ctxt, self.share_id, establish, delete=False)
136 self.assertRaises(
137 webob.exc.HTTPForbidden,
138 self.controller.delete,
139 req, self.share_id, key)
141 # test that nothing was deleted
142 data = db.share_metadata_get(self.userctxt, self.share_id)
143 if key in data: 143 ↛ 145line 143 didn't jump to line 145 because the condition on line 143 was always true
144 res_dict = {'meta': {key: data[key]}}
145 self.assertEqual(res_dict, {'meta': establish})
147 @ddt.data((AFFINITY_KEY, '/' + AFFINITY_KEY),
148 (ANTI_AFFINITY_KEY, '/' + ANTI_AFFINITY_KEY))
149 @ddt.unpack
150 def test_delete_affinities_admin(self, key, path):
151 req = fakes.HTTPRequest.blank(self.url + path)
152 req.method = 'DELETE'
153 req.content_type = "application/json"
154 admin_context = req.environ['manila.context'].elevated()
155 req.environ['manila.context'] = admin_context
156 establish = {key: 'share1'}
157 db.share_metadata_update(
158 self.ctxt, self.share_id, establish, delete=False)
160 self.controller.delete(
161 req, self.share_id, key)
163 # test that key was deleted
164 data = db.share_metadata_get(self.ctxt, self.share_id)
165 res_dict = {'meta': data}
166 self.assertEqual(res_dict, {'meta': self.origin_metadata})
168 def test_create(self):
169 req = fakes.HTTPRequest.blank('/v1/share_metadata')
170 req.method = 'POST'
171 req.content_type = "application/json"
172 body = {"metadata": {"key9": "value9"}}
173 req.body = jsonutils.dumps(body).encode("utf-8")
174 res_dict = self.controller.create(req, self.share_id, body)
175 expected = self.origin_metadata
176 expected.update(body['metadata'])
177 self.assertEqual({'metadata': expected}, res_dict)
179 def test_create_empty_body(self):
180 req = fakes.HTTPRequest.blank(self.url)
181 req.method = 'POST'
182 req.headers["content-type"] = "application/json"
184 self.assertRaises(webob.exc.HTTPBadRequest,
185 self.controller.create, req, self.share_id, None)
187 def test_create_item_empty_key(self):
188 req = fakes.HTTPRequest.blank(self.url + '/key1')
189 req.method = 'PUT'
190 body = {"meta": {"": "value1"}}
191 req.body = jsonutils.dumps(body).encode("utf-8")
192 req.headers["content-type"] = "application/json"
194 self.assertRaises(webob.exc.HTTPBadRequest,
195 self.controller.create, req, self.share_id, body)
197 def test_create_item_key_too_long(self):
198 req = fakes.HTTPRequest.blank(self.url + '/key1')
199 req.method = 'PUT'
200 body = {"meta": {("a" * 260): "value1"}}
201 req.body = jsonutils.dumps(body).encode("utf-8")
202 req.headers["content-type"] = "application/json"
204 self.assertRaises(webob.exc.HTTPBadRequest,
205 self.controller.create,
206 req, self.share_id, body)
208 def test_create_nonexistent_share(self):
209 req = fakes.HTTPRequest.blank('/v1/share_metadata')
210 req.method = 'POST'
211 req.content_type = "application/json"
212 body = {"metadata": {"key9": "value9"}}
213 req.body = jsonutils.dumps(body).encode("utf-8")
214 self.assertRaises(
215 webob.exc.HTTPNotFound,
216 self.controller.create,
217 req, "nonexistent_share", body)
219 def test_update_all(self):
220 req = fakes.HTTPRequest.blank(self.url)
221 req.method = 'PUT'
222 req.content_type = "application/json"
223 expected = {
224 'metadata': {
225 'key10': 'value10',
226 'key99': 'value99',
227 },
228 }
229 req.body = jsonutils.dumps(expected).encode("utf-8")
230 res_dict = self.controller.update_all(req, self.share_id, expected)
232 self.assertEqual(expected, res_dict)
234 def test_update_all_empty_container(self):
235 req = fakes.HTTPRequest.blank(self.url)
236 req.method = 'PUT'
237 req.content_type = "application/json"
238 expected = {'metadata': {}}
239 req.body = jsonutils.dumps(expected).encode("utf-8")
240 res_dict = self.controller.update_all(req, self.share_id, expected)
242 self.assertEqual(expected, res_dict)
244 def test_update_all_malformed_container(self):
245 req = fakes.HTTPRequest.blank(self.url)
246 req.method = 'PUT'
247 req.content_type = "application/json"
248 expected = {'meta': {}}
249 req.body = jsonutils.dumps(expected).encode("utf-8")
251 self.assertRaises(webob.exc.HTTPBadRequest,
252 self.controller.update_all, req, self.share_id,
253 expected)
255 @ddt.data(['asdf'],
256 {'key': None},
257 {None: 'value'},
258 {None: None})
259 def test_update_all_malformed_data(self, metadata):
260 req = fakes.HTTPRequest.blank(self.url)
261 req.method = 'PUT'
262 req.content_type = "application/json"
263 expected = {'metadata': metadata}
264 req.body = jsonutils.dumps(expected).encode("utf-8")
266 self.assertRaises(webob.exc.HTTPBadRequest,
267 self.controller.update_all, req, self.share_id,
268 expected)
270 def test_update_all_nonexistent_share(self):
271 req = fakes.HTTPRequest.blank(self.url)
272 req.method = 'PUT'
273 req.content_type = "application/json"
274 body = {'metadata': {'key10': 'value10'}}
275 req.body = jsonutils.dumps(body).encode("utf-8")
277 self.assertRaises(webob.exc.HTTPNotFound,
278 self.controller.update_all, req, '100', body)
280 @ddt.data({AFFINITY_KEY: 'foo'},
281 {ANTI_AFFINITY_KEY: 'foo'},
282 {AFFINITY_KEY: 'foo',
283 ANTI_AFFINITY_KEY: 'bar'},
284 {AFFINITY_KEY: 'foo',
285 ANTI_AFFINITY_KEY: 'bar',
286 'foo': 'bar'})
287 def test_update_all_affinities_user(self, metadata):
288 body = {'metadata': metadata}
289 self.userctxt = context.RequestContext('demo', 'fake', False)
290 req = fakes.HTTPRequest.blank(self.url)
291 req.method = 'PUT'
292 req.content_type = "application/json"
293 req.environ['manila.context'] = self.userctxt
294 establish = {AFFINITY_KEY: 'share1'}
295 db.share_metadata_update(
296 self.ctxt, self.share_id, establish, delete=False)
297 before_update_all = db.share_metadata_get(self.userctxt, self.share_id)
299 body = {'metadata': metadata}
300 req.body = jsonutils.dumps(body).encode("utf-8")
301 self.assertRaises(
302 webob.exc.HTTPForbidden,
303 self.controller.update_all,
304 req, self.share_id, body)
306 # test nothing was deleted or updated
307 after_update_all = db.share_metadata_get(self.userctxt, self.share_id)
308 self.assertEqual(after_update_all, before_update_all)
310 @ddt.data({AFFINITY_KEY: 'foo'},
311 {ANTI_AFFINITY_KEY: 'foo'},
312 {AFFINITY_KEY: 'foo',
313 ANTI_AFFINITY_KEY: 'bar'},
314 {AFFINITY_KEY: 'foo',
315 ANTI_AFFINITY_KEY: 'bar',
316 'foo': 'bar'})
317 def test_update_all_affinities_admin(self, metadata):
318 req = fakes.HTTPRequest.blank(self.url)
319 req.method = 'PUT'
320 req.content_type = "application/json"
321 admin_context = req.environ['manila.context'].elevated()
322 req.environ['manila.context'] = admin_context
323 establish = {AFFINITY_KEY: 'share1'}
324 db.share_metadata_update(
325 self.ctxt, self.share_id, establish, delete=False)
327 body = {'metadata': metadata}
328 req.body = jsonutils.dumps(body).encode("utf-8")
329 res_dict = self.controller.update_all(req, self.share_id, body)
330 expected = body
331 self.assertEqual(res_dict, expected)
333 def test_update_item(self):
334 req = fakes.HTTPRequest.blank(self.url + '/key1')
335 req.method = 'PUT'
336 body = {"meta": {"key1": "value1"}}
337 req.body = jsonutils.dumps(body).encode("utf-8")
338 req.headers["content-type"] = "application/json"
339 res_dict = self.controller.update(req, self.share_id, 'key1', body)
340 expected = {'meta': {'key1': 'value1'}}
341 self.assertEqual(expected, res_dict)
343 def test_update_item_nonexistent_share(self):
344 req = fakes.HTTPRequest.blank('/v1.1/fake/shares/asdf/metadata/key1')
345 req.method = 'PUT'
346 body = {"meta": {"key1": "value1"}}
347 req.body = jsonutils.dumps(body).encode("utf-8")
348 req.headers["content-type"] = "application/json"
350 self.assertRaises(
351 webob.exc.HTTPNotFound,
352 self.controller.update,
353 req, "nonexistent_share", 'key1', body)
355 def test_update_item_empty_body(self):
356 req = fakes.HTTPRequest.blank(self.url + '/key1')
357 req.method = 'PUT'
358 req.headers["content-type"] = "application/json"
360 self.assertRaises(webob.exc.HTTPBadRequest,
361 self.controller.update, req, self.share_id, 'key1',
362 None)
364 def test_update_item_empty_key(self):
365 req = fakes.HTTPRequest.blank(self.url + '/key1')
366 req.method = 'PUT'
367 body = {"meta": {"": "value1"}}
368 req.body = jsonutils.dumps(body).encode("utf-8")
369 req.headers["content-type"] = "application/json"
371 self.assertRaises(webob.exc.HTTPBadRequest,
372 self.controller.update, req, self.share_id, '', body)
374 def test_update_item_key_too_long(self):
375 req = fakes.HTTPRequest.blank(self.url + '/key1')
376 req.method = 'PUT'
377 body = {"meta": {("a" * 260): "value1"}}
378 req.body = jsonutils.dumps(body).encode("utf-8")
379 req.headers["content-type"] = "application/json"
381 self.assertRaises(webob.exc.HTTPBadRequest,
382 self.controller.update,
383 req, self.share_id, ("a" * 260), body)
385 def test_update_item_value_too_long(self):
386 req = fakes.HTTPRequest.blank(self.url + '/key1')
387 req.method = 'PUT'
388 body = {"meta": {"key1": ("a" * 1025)}}
389 req.body = jsonutils.dumps(body).encode("utf-8")
390 req.headers["content-type"] = "application/json"
392 self.assertRaises(webob.exc.HTTPBadRequest,
393 self.controller.update,
394 req, self.share_id, "key1", body)
396 def test_update_item_too_many_keys(self):
397 req = fakes.HTTPRequest.blank(self.url + '/key1')
398 req.method = 'PUT'
399 body = {"meta": {"key1": "value1", "key2": "value2"}}
400 req.body = jsonutils.dumps(body).encode("utf-8")
401 req.headers["content-type"] = "application/json"
403 self.assertRaises(webob.exc.HTTPBadRequest,
404 self.controller.update, req, self.share_id, 'key1',
405 body)
407 def test_update_item_body_uri_mismatch(self):
408 req = fakes.HTTPRequest.blank(self.url + '/bad')
409 req.method = 'PUT'
410 body = {"meta": {"key1": "value1"}}
411 req.body = jsonutils.dumps(body).encode("utf-8")
412 req.headers["content-type"] = "application/json"
414 self.assertRaises(webob.exc.HTTPBadRequest,
415 self.controller.update, req, self.share_id, 'bad',
416 body)
418 @ddt.data((AFFINITY_KEY, '/' + AFFINITY_KEY),
419 (ANTI_AFFINITY_KEY, '/' + ANTI_AFFINITY_KEY))
420 @ddt.unpack
421 def test_update_item_affinities_user(self, key, path):
422 self.userctxt = context.RequestContext('demo', 'fake', False)
423 req = fakes.HTTPRequest.blank(self.url + path)
424 req.method = 'PUT'
425 req.content_type = "application/json"
426 req.environ['manila.context'] = self.userctxt
427 establish = {AFFINITY_KEY: 'share1'}
428 db.share_metadata_update(
429 self.ctxt, self.share_id, establish, delete=False)
431 body = {'meta': {key: 'share1,share2'}}
432 req.body = jsonutils.dumps(body).encode("utf-8")
433 self.assertRaises(
434 webob.exc.HTTPForbidden,
435 self.controller.update,
436 req, self.share_id, key, body)
438 # test that nothing was updated
439 data = db.share_metadata_get(self.ctxt, self.share_id)
440 if AFFINITY_KEY in data: 440 ↛ 442line 440 didn't jump to line 442 because the condition on line 440 was always true
441 res_dict = {'meta': {AFFINITY_KEY: data[AFFINITY_KEY]}}
442 self.assertEqual(res_dict, {'meta': establish})
444 @ddt.data((AFFINITY_KEY, '/' + AFFINITY_KEY),
445 (ANTI_AFFINITY_KEY, '/' + ANTI_AFFINITY_KEY))
446 @ddt.unpack
447 def test_update_item_affinities_admin(self, key, path):
448 req = fakes.HTTPRequest.blank(self.url + path)
449 req.method = 'PUT'
450 req.content_type = "application/json"
451 admin_context = req.environ['manila.context'].elevated()
452 req.environ['manila.context'] = admin_context
453 establish = {AFFINITY_KEY: 'share1'}
454 db.share_metadata_update(
455 self.ctxt, self.share_id, establish, delete=False)
457 body = {'meta': {key: 'share1,share2'}}
458 req.body = jsonutils.dumps(body).encode("utf-8")
459 res_dict = self.controller.update(
460 req, self.share_id, key, body)
461 expected = body
462 self.assertEqual(res_dict, expected)
464 def test_invalid_metadata_items_on_create(self):
465 req = fakes.HTTPRequest.blank(self.url)
466 req.method = 'POST'
467 req.headers["content-type"] = "application/json"
469 # test for long key
470 data = {"metadata": {"a" * 260: "value1"}}
471 req.body = jsonutils.dumps(data).encode("utf-8")
472 self.assertRaises(webob.exc.HTTPBadRequest,
473 self.controller.create, req, self.share_id, data)
475 # test for long value
476 data = {"metadata": {"key": "v" * 1025}}
477 req.body = jsonutils.dumps(data).encode("utf-8")
478 self.assertRaises(webob.exc.HTTPBadRequest,
479 self.controller.create, req, self.share_id, data)
481 # test for empty key.
482 data = {"metadata": {"": "value1"}}
483 req.body = jsonutils.dumps(data).encode("utf-8")
484 self.assertRaises(webob.exc.HTTPBadRequest,
485 self.controller.create, req, self.share_id, data)