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

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. 

15 

16import ddt 

17from oslo_config import cfg 

18from oslo_serialization import jsonutils 

19import webob 

20 

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 

29 

30CONF = cfg.CONF 

31AFFINITY_KEY = constants.AdminOnlyMetadata.AFFINITY_KEY 

32ANTI_AFFINITY_KEY = constants.AdminOnlyMetadata.ANTI_AFFINITY_KEY 

33 

34 

35@ddt.ddt 

36class ShareMetaDataTest(test.TestCase): 

37 

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) 

54 

55 def test_index(self): 

56 req = fakes.HTTPRequest.blank(self.url) 

57 res_dict = self.controller.index(req, self.share_id) 

58 

59 expected = { 

60 'metadata': { 

61 'key1': 'value1', 

62 'key2': 'value2', 

63 'key3': 'value3', 

64 }, 

65 } 

66 

67 self.assertEqual(expected, res_dict) 

68 

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) 

73 

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) 

81 

82 def test_show(self): 

83 req = fakes.HTTPRequest.blank(self.url + '/key2') 

84 

85 res_dict = self.controller.show(req, self.share_id, 'key2') 

86 

87 expected = {'meta': {'key2': 'value2'}} 

88 self.assertEqual(expected, res_dict) 

89 

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

96 

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

101 

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

106 

107 self.assertEqual(200, res.status_int) 

108 

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

116 

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

122 

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) 

135 

136 self.assertRaises( 

137 webob.exc.HTTPForbidden, 

138 self.controller.delete, 

139 req, self.share_id, key) 

140 

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

146 

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) 

159 

160 self.controller.delete( 

161 req, self.share_id, key) 

162 

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

167 

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) 

178 

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" 

183 

184 self.assertRaises(webob.exc.HTTPBadRequest, 

185 self.controller.create, req, self.share_id, None) 

186 

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" 

193 

194 self.assertRaises(webob.exc.HTTPBadRequest, 

195 self.controller.create, req, self.share_id, body) 

196 

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" 

203 

204 self.assertRaises(webob.exc.HTTPBadRequest, 

205 self.controller.create, 

206 req, self.share_id, body) 

207 

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) 

218 

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) 

231 

232 self.assertEqual(expected, res_dict) 

233 

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) 

241 

242 self.assertEqual(expected, res_dict) 

243 

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

250 

251 self.assertRaises(webob.exc.HTTPBadRequest, 

252 self.controller.update_all, req, self.share_id, 

253 expected) 

254 

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

265 

266 self.assertRaises(webob.exc.HTTPBadRequest, 

267 self.controller.update_all, req, self.share_id, 

268 expected) 

269 

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

276 

277 self.assertRaises(webob.exc.HTTPNotFound, 

278 self.controller.update_all, req, '100', body) 

279 

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) 

298 

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) 

305 

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) 

309 

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) 

326 

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) 

332 

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) 

342 

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" 

349 

350 self.assertRaises( 

351 webob.exc.HTTPNotFound, 

352 self.controller.update, 

353 req, "nonexistent_share", 'key1', body) 

354 

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" 

359 

360 self.assertRaises(webob.exc.HTTPBadRequest, 

361 self.controller.update, req, self.share_id, 'key1', 

362 None) 

363 

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" 

370 

371 self.assertRaises(webob.exc.HTTPBadRequest, 

372 self.controller.update, req, self.share_id, '', body) 

373 

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" 

380 

381 self.assertRaises(webob.exc.HTTPBadRequest, 

382 self.controller.update, 

383 req, self.share_id, ("a" * 260), body) 

384 

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" 

391 

392 self.assertRaises(webob.exc.HTTPBadRequest, 

393 self.controller.update, 

394 req, self.share_id, "key1", body) 

395 

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" 

402 

403 self.assertRaises(webob.exc.HTTPBadRequest, 

404 self.controller.update, req, self.share_id, 'key1', 

405 body) 

406 

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" 

413 

414 self.assertRaises(webob.exc.HTTPBadRequest, 

415 self.controller.update, req, self.share_id, 'bad', 

416 body) 

417 

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) 

430 

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) 

437 

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

443 

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) 

456 

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) 

463 

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" 

468 

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) 

474 

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) 

480 

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)