summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWayne Okuma <wayne.okuma@hp.com>2015-06-25 17:07:01 -0700
committerWayne Okuma <wayne.okuma@hp.com>2015-09-02 22:32:29 -0700
commit5369e86e8d521115228573dd1878d764cd249e97 (patch)
treea1dedc67492dd2b1dcdfba437aca7866d07ce15e
parentb2c2ecee50407a40d280231fbc89c79afd8f1cb6 (diff)
Glance metadef tables need unique constraints.
Sometime during Kilo, the unique constraints on metadef_namespaces (namespace) metadef_objects(namespace_id, name) metadef_properties(namespace_id, name) metadef_tags(namespace_id, name) metadef_resource_types(name) were replaced with non-unique indices. I believe this was done erroneously to make the migrate_repo/versions/scripts match the db/sqlalchemy/models_metadef.py definitions. Unfortunately, the schema scripts were correct with the unique constraints and what should have changed was models_metadef.py. This bug, puts one more migrate script in place which will rename any duplicate records it finds to make them unique and then re-establishes the unique constraints while dropping the non-unique indices. It also, fixes models_metadef.py and adds in tests to attempt to create duplicates which should result in an HTTPConflict. Change-Id: Idf3569a27d64abea3ed6ec92fb77b36a4d6d5fd5 Closes-Bug: 1468946
Notes
Notes (review): Verified+2: Jenkins Code-Review+2: Stuart McLaren <stuart.mclaren@hp.com> Code-Review+2: Flavio Percoco <fpercoco@redhat.com> Workflow+1: Flavio Percoco <fpercoco@redhat.com> Code-Review+1: Wayne Okuma <wayne.okuma@hp.com> Code-Review+1: Swami Reddy <swamireddy@gmail.com> Submitted-by: Jenkins Submitted-at: Fri, 11 Sep 2015 17:13:06 +0000 Reviewed-on: https://review.openstack.org/195820 Project: openstack/glance Branch: refs/heads/master
-rwxr-xr-xglance/db/sqlalchemy/migrate_repo/versions/042_add_changes_to_reinstall_unique_metadef_constraints.py604
-rw-r--r--glance/db/sqlalchemy/models_metadef.py42
-rw-r--r--glance/tests/functional/db/base_metadef.py106
-rw-r--r--glance/tests/functional/v2/test_metadef_namespaces.py4
-rw-r--r--glance/tests/functional/v2/test_metadef_objects.py4
-rw-r--r--glance/tests/functional/v2/test_metadef_properties.py4
-rw-r--r--glance/tests/functional/v2/test_metadef_tags.py5
-rw-r--r--glance/tests/unit/test_migrations.py202
-rw-r--r--glance/tests/unit/v2/test_metadef_resources.py38
9 files changed, 994 insertions, 15 deletions
diff --git a/glance/db/sqlalchemy/migrate_repo/versions/042_add_changes_to_reinstall_unique_metadef_constraints.py b/glance/db/sqlalchemy/migrate_repo/versions/042_add_changes_to_reinstall_unique_metadef_constraints.py
new file mode 100755
index 0000000..836e953
--- /dev/null
+++ b/glance/db/sqlalchemy/migrate_repo/versions/042_add_changes_to_reinstall_unique_metadef_constraints.py
@@ -0,0 +1,604 @@
1
2# Licensed under the Apache License, Version 2.0 (the "License"); you may
3# not use this file except in compliance with the License. You may obtain
4# a copy of the License at
5#
6# http://www.apache.org/licenses/LICENSE-2.0
7#
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11# License for the specific language governing permissions and limitations
12# under the License.
13
14import migrate
15import sqlalchemy
16from sqlalchemy import (func, Index, inspect, orm, String, Table, type_coerce)
17
18
19# The _upgrade...get_duplicate() def's are separate functions to
20# accommodate sqlite which locks the database against updates as long as
21# db_recs is active.
22# In addition, sqlite doesn't support the function 'concat' between
23# Strings and Integers, so, the updating of records is also adjusted.
24def _upgrade_metadef_namespaces_get_duplicates(migrate_engine):
25 meta = sqlalchemy.schema.MetaData(migrate_engine)
26 metadef_namespaces = Table('metadef_namespaces', meta, autoload=True)
27
28 session = orm.sessionmaker(bind=migrate_engine)()
29 db_recs = (session.query(func.min(metadef_namespaces.c.id),
30 metadef_namespaces.c.namespace)
31 .group_by(metadef_namespaces.c.namespace)
32 .having(func.count(metadef_namespaces.c.namespace) > 1))
33 dbrecs = []
34 for row in db_recs:
35 dbrecs.append({'id': row[0], 'namespace': row[1]})
36 session.close()
37
38 return dbrecs
39
40
41def _upgrade_metadef_objects_get_duplicates(migrate_engine):
42 meta = sqlalchemy.schema.MetaData(migrate_engine)
43 metadef_objects = Table('metadef_objects', meta, autoload=True)
44
45 session = orm.sessionmaker(bind=migrate_engine)()
46 db_recs = (session.query(func.min(metadef_objects.c.id),
47 metadef_objects.c.namespace_id,
48 metadef_objects.c.name)
49 .group_by(metadef_objects.c.namespace_id,
50 metadef_objects.c.name)
51 .having(func.count() > 1))
52 dbrecs = []
53 for row in db_recs:
54 dbrecs.append({'id': row[0], 'namespace_id': row[1], 'name': row[2]})
55 session.close()
56
57 return dbrecs
58
59
60def _upgrade_metadef_properties_get_duplicates(migrate_engine):
61 meta = sqlalchemy.schema.MetaData(migrate_engine)
62 metadef_properties = Table('metadef_properties', meta, autoload=True)
63
64 session = orm.sessionmaker(bind=migrate_engine)()
65 db_recs = (session.query(func.min(metadef_properties.c.id),
66 metadef_properties.c.namespace_id,
67 metadef_properties.c.name)
68 .group_by(metadef_properties.c.namespace_id,
69 metadef_properties.c.name)
70 .having(func.count() > 1))
71 dbrecs = []
72 for row in db_recs:
73 dbrecs.append({'id': row[0], 'namespace_id': row[1], 'name': row[2]})
74 session.close()
75
76 return dbrecs
77
78
79def _upgrade_metadef_tags_get_duplicates(migrate_engine):
80 meta = sqlalchemy.schema.MetaData(migrate_engine)
81 metadef_tags = Table('metadef_tags', meta, autoload=True)
82
83 session = orm.sessionmaker(bind=migrate_engine)()
84 db_recs = (session.query(func.min(metadef_tags.c.id),
85 metadef_tags.c.namespace_id,
86 metadef_tags.c.name)
87 .group_by(metadef_tags.c.namespace_id,
88 metadef_tags.c.name)
89 .having(func.count() > 1))
90 dbrecs = []
91 for row in db_recs:
92 dbrecs.append({'id': row[0], 'namespace_id': row[1], 'name': row[2]})
93 session.close()
94
95 return dbrecs
96
97
98def _upgrade_metadef_resource_types_get_duplicates(migrate_engine):
99 meta = sqlalchemy.schema.MetaData(migrate_engine)
100 metadef_resource_types = Table('metadef_resource_types', meta,
101 autoload=True)
102
103 session = orm.sessionmaker(bind=migrate_engine)()
104 db_recs = (session.query(func.min(metadef_resource_types.c.id),
105 metadef_resource_types.c.name)
106 .group_by(metadef_resource_types.c.name)
107 .having(func.count(metadef_resource_types.c.name) > 1))
108 dbrecs = []
109 for row in db_recs:
110 dbrecs.append({'id': row[0], 'name': row[1]})
111 session.close()
112
113 return dbrecs
114
115
116def _upgrade_data(migrate_engine):
117 # Rename duplicates to be unique.
118 meta = sqlalchemy.schema.MetaData(migrate_engine)
119
120 # ORM tables
121 metadef_namespaces = Table('metadef_namespaces', meta, autoload=True)
122 metadef_objects = Table('metadef_objects', meta, autoload=True)
123 metadef_properties = Table('metadef_properties', meta, autoload=True)
124 metadef_tags = Table('metadef_tags', meta, autoload=True)
125 metadef_resource_types = Table('metadef_resource_types', meta,
126 autoload=True)
127
128 # Fix duplicate metadef_namespaces
129 # Update the non-first record(s) with an unique namespace value
130 dbrecs = _upgrade_metadef_namespaces_get_duplicates(migrate_engine)
131 for row in dbrecs:
132 s = (metadef_namespaces.update()
133 .where(metadef_namespaces.c.id > row['id'])
134 .where(metadef_namespaces.c.namespace == row['namespace'])
135 )
136 if migrate_engine.name == 'sqlite':
137 s = (s.values(namespace=(row['namespace'] + '-DUPL-' +
138 type_coerce(metadef_namespaces.c.id,
139 String)),
140 display_name=(row['namespace'] + '-DUPL-' +
141 type_coerce(metadef_namespaces.c.id,
142 String))))
143 else:
144 s = s.values(namespace=func.concat(row['namespace'],
145 '-DUPL-',
146 metadef_namespaces.c.id),
147 display_name=func.concat(row['namespace'],
148 '-DUPL-',
149 metadef_namespaces.c.id))
150 s.execute()
151
152 # Fix duplicate metadef_objects
153 dbrecs = _upgrade_metadef_objects_get_duplicates(migrate_engine)
154 for row in dbrecs:
155 s = (metadef_objects.update()
156 .where(metadef_objects.c.id > row['id'])
157 .where(metadef_objects.c.namespace_id == row['namespace_id'])
158 .where(metadef_objects.c.name == str(row['name']))
159 )
160 if migrate_engine.name == 'sqlite':
161 s = (s.values(name=(row['name'] + '-DUPL-'
162 + type_coerce(metadef_objects.c.id, String))))
163 else:
164 s = s.values(name=func.concat(row['name'], '-DUPL-',
165 metadef_objects.c.id))
166 s.execute()
167
168 # Fix duplicate metadef_properties
169 dbrecs = _upgrade_metadef_properties_get_duplicates(migrate_engine)
170 for row in dbrecs:
171 s = (metadef_properties.update()
172 .where(metadef_properties.c.id > row['id'])
173 .where(metadef_properties.c.namespace_id == row['namespace_id'])
174 .where(metadef_properties.c.name == str(row['name']))
175 )
176 if migrate_engine.name == 'sqlite':
177 s = (s.values(name=(row['name'] + '-DUPL-' +
178 type_coerce(metadef_properties.c.id, String)))
179 )
180 else:
181 s = s.values(name=func.concat(row['name'], '-DUPL-',
182 metadef_properties.c.id))
183 s.execute()
184
185 # Fix duplicate metadef_tags
186 dbrecs = _upgrade_metadef_tags_get_duplicates(migrate_engine)
187 for row in dbrecs:
188 s = (metadef_tags.update()
189 .where(metadef_tags.c.id > row['id'])
190 .where(metadef_tags.c.namespace_id == row['namespace_id'])
191 .where(metadef_tags.c.name == str(row['name']))
192 )
193 if migrate_engine.name == 'sqlite':
194 s = (s.values(name=(row['name'] + '-DUPL-' +
195 type_coerce(metadef_tags.c.id, String)))
196 )
197 else:
198 s = s.values(name=func.concat(row['name'], '-DUPL-',
199 metadef_tags.c.id))
200 s.execute()
201
202 # Fix duplicate metadef_resource_types
203 dbrecs = _upgrade_metadef_resource_types_get_duplicates(migrate_engine)
204 for row in dbrecs:
205 s = (metadef_resource_types.update()
206 .where(metadef_resource_types.c.id > row['id'])
207 .where(metadef_resource_types.c.name == str(row['name']))
208 )
209 if migrate_engine.name == 'sqlite':
210 s = (s.values(name=(row['name'] + '-DUPL-' +
211 type_coerce(metadef_resource_types.c.id,
212 String)))
213 )
214 else:
215 s = s.values(name=func.concat(row['name'], '-DUPL-',
216 metadef_resource_types.c.id))
217 s.execute()
218
219
220def _update_sqlite_namespace_id_name_constraint(metadef, metadef_namespaces,
221 new_constraint_name,
222 new_fk_name):
223 migrate.UniqueConstraint(
224 metadef.c.namespace_id, metadef.c.name).drop()
225 migrate.UniqueConstraint(
226 metadef.c.namespace_id, metadef.c.name,
227 name=new_constraint_name).create()
228 migrate.ForeignKeyConstraint(
229 [metadef.c.namespace_id],
230 [metadef_namespaces.c.id],
231 name=new_fk_name).create()
232
233
234def _downgrade_sqlite_namespace_id_name_constraint(metadef,
235 metadef_namespaces,
236 constraint_name,
237 fk_name):
238 migrate.UniqueConstraint(
239 metadef.c.namespace_id,
240 metadef.c.name,
241 name=constraint_name).drop()
242 migrate.UniqueConstraint(
243 metadef.c.namespace_id,
244 metadef.c.name).create()
245
246 migrate.ForeignKeyConstraint(
247 [metadef.c.namespace_id],
248 [metadef_namespaces.c.id],
249 name=fk_name).drop()
250 migrate.ForeignKeyConstraint(
251 [metadef.c.namespace_id],
252 [metadef_namespaces.c.id]).create()
253
254
255def _drop_unique_constraint_if_exists(inspector, table_name, metadef):
256 name = _get_unique_constraint_name(inspector,
257 table_name,
258 ['namespace_id', 'name'])
259 if name:
260 migrate.UniqueConstraint(metadef.c.namespace_id,
261 metadef.c.name,
262 name=name).drop()
263
264
265def _drop_index_with_fk_constraint(metadef, metadef_namespaces,
266 index_name,
267 fk_old_name, fk_new_name):
268
269 fkc = migrate.ForeignKeyConstraint([metadef.c.namespace_id],
270 [metadef_namespaces.c.id],
271 name=fk_old_name)
272 fkc.drop()
273
274 if index_name:
275 Index(index_name, metadef.c.namespace_id).drop()
276
277 # Rename the fk for consistency across all db's
278 fkc = migrate.ForeignKeyConstraint([metadef.c.namespace_id],
279 [metadef_namespaces.c.id],
280 name=fk_new_name)
281 fkc.create()
282
283
284def _downgrade_constraint_with_fk(metadef, metadef_namespaces,
285 constraint_name,
286 fk_curr_name, fk_next_name):
287
288 fkc = migrate.ForeignKeyConstraint([metadef.c.namespace_id],
289 [metadef_namespaces.c.id],
290 name=fk_curr_name)
291 fkc.drop()
292
293 migrate.UniqueConstraint(metadef.c.namespace_id, metadef.c.name,
294 name=constraint_name).drop()
295
296 fkc = migrate.ForeignKeyConstraint([metadef.c.namespace_id],
297 [metadef_namespaces.c.id],
298 name=fk_next_name)
299 fkc.create()
300
301
302def _get_unique_constraint_name(inspector, table_name, columns):
303 constraints = inspector.get_unique_constraints(table_name)
304 for constraint in constraints:
305 if set(constraint['column_names']) == set(columns):
306 return constraint['name']
307 return None
308
309
310def _get_fk_constraint_name(inspector, table_name, columns):
311 constraints = inspector.get_foreign_keys(table_name)
312 for constraint in constraints:
313 if set(constraint['constrained_columns']) == set(columns):
314 return constraint['name']
315 return None
316
317
318def upgrade(migrate_engine):
319
320 _upgrade_data(migrate_engine)
321
322 meta = sqlalchemy.MetaData()
323 meta.bind = migrate_engine
324 inspector = inspect(migrate_engine)
325
326 # ORM tables
327 metadef_namespaces = Table('metadef_namespaces', meta, autoload=True)
328 metadef_objects = Table('metadef_objects', meta, autoload=True)
329 metadef_properties = Table('metadef_properties', meta, autoload=True)
330 metadef_tags = Table('metadef_tags', meta, autoload=True)
331 metadef_ns_res_types = Table('metadef_namespace_resource_types',
332 meta, autoload=True)
333 metadef_resource_types = Table('metadef_resource_types', meta,
334 autoload=True)
335
336 # Drop the bad, non-unique indices.
337 if migrate_engine.name == 'sqlite':
338 # For sqlite:
339 # Only after the unique constraints have been added should the indices
340 # be dropped. If done the other way, sqlite complains during
341 # constraint adding/dropping that the index does/does not exist.
342 # Note: The _get_unique_constraint_name, _get_fk_constraint_name
343 # return None for constraints that do in fact exist. Also,
344 # get_index_names returns names, but, the names can not be used with
345 # the Index(name, blah).drop() command, so, putting sqlite into
346 # it's own section.
347
348 # Objects
349 _update_sqlite_namespace_id_name_constraint(
350 metadef_objects, metadef_namespaces,
351 'uq_metadef_objects_namespace_id_name',
352 'metadef_objects_fk_1')
353
354 # Properties
355 _update_sqlite_namespace_id_name_constraint(
356 metadef_properties, metadef_namespaces,
357 'uq_metadef_properties_namespace_id_name',
358 'metadef_properties_fk_1')
359
360 # Tags
361 _update_sqlite_namespace_id_name_constraint(
362 metadef_tags, metadef_namespaces,
363 'uq_metadef_tags_namespace_id_name',
364 'metadef_tags_fk_1')
365
366 # Namespaces
367 migrate.UniqueConstraint(
368 metadef_namespaces.c.namespace).drop()
369 migrate.UniqueConstraint(
370 metadef_namespaces.c.namespace,
371 name='uq_metadef_namespaces_namespace').create()
372
373 # ResourceTypes
374 migrate.UniqueConstraint(
375 metadef_resource_types.c.name).drop()
376 migrate.UniqueConstraint(
377 metadef_resource_types.c.name,
378 name='uq_metadef_resource_types_name').create()
379
380 # Now drop the bad indices
381 Index('ix_metadef_objects_namespace_id',
382 metadef_objects.c.namespace_id,
383 metadef_objects.c.name).drop()
384 Index('ix_metadef_properties_namespace_id',
385 metadef_properties.c.namespace_id,
386 metadef_properties.c.name).drop()
387 Index('ix_metadef_tags_namespace_id',
388 metadef_tags.c.namespace_id,
389 metadef_tags.c.name).drop()
390 else:
391 # First drop the bad non-unique indices.
392 # To do that (for mysql), must first drop foreign key constraints
393 # BY NAME and then drop the bad indices.
394 # Finally, re-create the foreign key constraints with a consistent
395 # name.
396
397 # DB2 still has unique constraints, but, they are badly named.
398 # Drop them, they will be recreated at the final step.
399 name = _get_unique_constraint_name(inspector, 'metadef_namespaces',
400 ['namespace'])
401 if name:
402 migrate.UniqueConstraint(metadef_namespaces.c.namespace,
403 name=name).drop()
404 _drop_unique_constraint_if_exists(inspector, 'metadef_objects',
405 metadef_objects)
406 _drop_unique_constraint_if_exists(inspector, 'metadef_properties',
407 metadef_properties)
408 _drop_unique_constraint_if_exists(inspector, 'metadef_tags',
409 metadef_tags)
410 name = _get_unique_constraint_name(inspector, 'metadef_resource_types',
411 ['name'])
412 if name:
413 migrate.UniqueConstraint(metadef_resource_types.c.name,
414 name=name).drop()
415
416 # Objects
417 _drop_index_with_fk_constraint(
418 metadef_objects, metadef_namespaces,
419 'ix_metadef_objects_namespace_id',
420 _get_fk_constraint_name(
421 inspector, 'metadef_objects', ['namespace_id']),
422 'metadef_objects_fk_1')
423
424 # Properties
425 _drop_index_with_fk_constraint(
426 metadef_properties, metadef_namespaces,
427 'ix_metadef_properties_namespace_id',
428 _get_fk_constraint_name(
429 inspector, 'metadef_properties', ['namespace_id']),
430 'metadef_properties_fk_1')
431
432 # Tags
433 _drop_index_with_fk_constraint(
434 metadef_tags, metadef_namespaces,
435 'ix_metadef_tags_namespace_id',
436 _get_fk_constraint_name(
437 inspector, 'metadef_tags', ['namespace_id']),
438 'metadef_tags_fk_1')
439
440 # Drop Others without fk constraints.
441 Index('ix_metadef_namespaces_namespace',
442 metadef_namespaces.c.namespace).drop()
443
444 # The next two don't exist in ibm_db_sa, but, drop them everywhere else.
445 if migrate_engine.name != 'ibm_db_sa':
446 Index('ix_metadef_resource_types_name',
447 metadef_resource_types.c.name).drop()
448 # Not needed due to primary key on same columns
449 Index('ix_metadef_ns_res_types_res_type_id_ns_id',
450 metadef_ns_res_types.c.resource_type_id,
451 metadef_ns_res_types.c.namespace_id).drop()
452
453 # Now, add back the dropped indexes as unique constraints
454 if migrate_engine.name != 'sqlite':
455 # Namespaces
456 migrate.UniqueConstraint(
457 metadef_namespaces.c.namespace,
458 name='uq_metadef_namespaces_namespace').create()
459
460 # Objects
461 migrate.UniqueConstraint(
462 metadef_objects.c.namespace_id,
463 metadef_objects.c.name,
464 name='uq_metadef_objects_namespace_id_name').create()
465
466 # Properties
467 migrate.UniqueConstraint(
468 metadef_properties.c.namespace_id,
469 metadef_properties.c.name,
470 name='uq_metadef_properties_namespace_id_name').create()
471
472 # Tags
473 migrate.UniqueConstraint(
474 metadef_tags.c.namespace_id,
475 metadef_tags.c.name,
476 name='uq_metadef_tags_namespace_id_name').create()
477
478 # Resource Types
479 migrate.UniqueConstraint(
480 metadef_resource_types.c.name,
481 name='uq_metadef_resource_types_name').create()
482
483
484def downgrade(migrate_engine):
485 meta = sqlalchemy.MetaData()
486 meta.bind = migrate_engine
487
488 # ORM tables
489 metadef_namespaces = Table('metadef_namespaces', meta, autoload=True)
490 metadef_objects = Table('metadef_objects', meta, autoload=True)
491 metadef_properties = Table('metadef_properties', meta, autoload=True)
492 metadef_tags = Table('metadef_tags', meta, autoload=True)
493 metadef_resource_types = Table('metadef_resource_types', meta,
494 autoload=True)
495 metadef_ns_res_types = Table('metadef_namespace_resource_types',
496 meta, autoload=True)
497
498 # Drop the unique constraints
499 if migrate_engine.name == 'sqlite':
500 # Objects
501 _downgrade_sqlite_namespace_id_name_constraint(
502 metadef_objects, metadef_namespaces,
503 'uq_metadef_objects_namespace_id_name',
504 'metadef_objects_fk_1')
505
506 # Properties
507 _downgrade_sqlite_namespace_id_name_constraint(
508 metadef_properties, metadef_namespaces,
509 'uq_metadef_properties_namespace_id_name',
510 'metadef_properties_fk_1')
511
512 # Tags
513 _downgrade_sqlite_namespace_id_name_constraint(
514 metadef_tags, metadef_namespaces,
515 'uq_metadef_tags_namespace_id_name',
516 'metadef_tags_fk_1')
517
518 # Namespaces
519 migrate.UniqueConstraint(
520 metadef_namespaces.c.namespace,
521 name='uq_metadef_namespaces_namespace').drop()
522 migrate.UniqueConstraint(
523 metadef_namespaces.c.namespace).create()
524
525 # ResourceTypes
526 migrate.UniqueConstraint(
527 metadef_resource_types.c.name,
528 name='uq_metadef_resource_types_name').drop()
529 migrate.UniqueConstraint(
530 metadef_resource_types.c.name).create()
531 else:
532 # For mysql, must drop foreign key constraints before dropping the
533 # unique constraint. So drop the fkc, then drop the constraints,
534 # then recreate the fkc.
535
536 # Objects
537 _downgrade_constraint_with_fk(
538 metadef_objects, metadef_namespaces,
539 'uq_metadef_objects_namespace_id_name',
540 'metadef_objects_fk_1', None)
541
542 # Properties
543 _downgrade_constraint_with_fk(
544 metadef_properties, metadef_namespaces,
545 'uq_metadef_properties_namespace_id_name',
546 'metadef_properties_fk_1', None)
547
548 # Tags
549 _downgrade_constraint_with_fk(
550 metadef_tags, metadef_namespaces,
551 'uq_metadef_tags_namespace_id_name',
552 'metadef_tags_fk_1', 'metadef_tags_namespace_id_fkey')
553
554 # Namespaces
555 migrate.UniqueConstraint(
556 metadef_namespaces.c.namespace,
557 name='uq_metadef_namespaces_namespace').drop()
558
559 # Resource_types
560 migrate.UniqueConstraint(
561 metadef_resource_types.c.name,
562 name='uq_metadef_resource_types_name').drop()
563
564 # Create dropped unique constraints as bad, non-unique indexes
565 Index('ix_metadef_objects_namespace_id',
566 metadef_objects.c.namespace_id).create()
567 Index('ix_metadef_properties_namespace_id',
568 metadef_properties.c.namespace_id).create()
569
570 # These need to be done before the metadef_tags and metadef_namespaces
571 # unique constraints are created to avoid 'tuple out of range' errors
572 # in db2.
573 Index('ix_metadef_tags_namespace_id',
574 metadef_tags.c.namespace_id,
575 metadef_tags.c.name).create()
576 Index('ix_metadef_namespaces_namespace',
577 metadef_namespaces.c.namespace).create()
578
579 # Create these everywhere, except for db2
580 if migrate_engine.name != 'ibm_db_sa':
581 Index('ix_metadef_resource_types_name',
582 metadef_resource_types.c.name).create()
583 Index('ix_metadef_ns_res_types_res_type_id_ns_id',
584 metadef_ns_res_types.c.resource_type_id,
585 metadef_ns_res_types.c.namespace_id).create()
586 else:
587 # Recreate the badly named unique constraints in db2
588 migrate.UniqueConstraint(
589 metadef_namespaces.c.namespace,
590 name='ix_namespaces_namespace').create()
591 migrate.UniqueConstraint(
592 metadef_objects.c.namespace_id,
593 metadef_objects.c.name,
594 name='ix_objects_namespace_id_name').create()
595 migrate.UniqueConstraint(
596 metadef_properties.c.namespace_id,
597 metadef_properties.c.name,
598 name='ix_metadef_properties_namespace_id_name').create()
599 migrate.UniqueConstraint(
600 metadef_tags.c.namespace_id,
601 metadef_tags.c.name).create()
602 migrate.UniqueConstraint(
603 metadef_resource_types.c.name,
604 name='ix_metadef_resource_types_name').create()
diff --git a/glance/db/sqlalchemy/models_metadef.py b/glance/db/sqlalchemy/models_metadef.py
index caf2bf8..4ff7631 100644
--- a/glance/db/sqlalchemy/models_metadef.py
+++ b/glance/db/sqlalchemy/models_metadef.py
@@ -28,6 +28,7 @@ from sqlalchemy import Integer
28from sqlalchemy.orm import relationship 28from sqlalchemy.orm import relationship
29from sqlalchemy import String 29from sqlalchemy import String
30from sqlalchemy import Text 30from sqlalchemy import Text
31from sqlalchemy import UniqueConstraint
31 32
32from glance.db.sqlalchemy.models import JSONEncodedDict 33from glance.db.sqlalchemy.models import JSONEncodedDict
33 34
@@ -65,8 +66,11 @@ class GlanceMetadefBase(models.TimestampMixin):
65class MetadefNamespace(BASE_DICT, GlanceMetadefBase): 66class MetadefNamespace(BASE_DICT, GlanceMetadefBase):
66 """Represents a metadata-schema namespace in the datastore.""" 67 """Represents a metadata-schema namespace in the datastore."""
67 __tablename__ = 'metadef_namespaces' 68 __tablename__ = 'metadef_namespaces'
68 __table_args__ = (Index('ix_metadef_namespaces_namespace', 'namespace'), 69 __table_args__ = (UniqueConstraint('namespace',
69 Index('ix_metadef_namespaces_owner', 'owner')) 70 name='uq_metadef_namespaces'
71 '_namespace'),
72 Index('ix_metadef_namespaces_owner', 'owner')
73 )
70 74
71 id = Column(Integer, primary_key=True, nullable=False) 75 id = Column(Integer, primary_key=True, nullable=False)
72 namespace = Column(String(80), nullable=False) 76 namespace = Column(String(80), nullable=False)
@@ -80,8 +84,11 @@ class MetadefNamespace(BASE_DICT, GlanceMetadefBase):
80class MetadefObject(BASE_DICT, GlanceMetadefBase): 84class MetadefObject(BASE_DICT, GlanceMetadefBase):
81 """Represents a metadata-schema object in the datastore.""" 85 """Represents a metadata-schema object in the datastore."""
82 __tablename__ = 'metadef_objects' 86 __tablename__ = 'metadef_objects'
83 __table_args__ = (Index('ix_metadef_objects_namespace_id', 'namespace_id'), 87 __table_args__ = (UniqueConstraint('namespace_id', 'name',
84 Index('ix_metadef_objects_name', 'name')) 88 name='uq_metadef_objects_namespace_id'
89 '_name'),
90 Index('ix_metadef_objects_name', 'name')
91 )
85 92
86 id = Column(Integer, primary_key=True, nullable=False) 93 id = Column(Integer, primary_key=True, nullable=False)
87 namespace_id = Column(Integer(), ForeignKey('metadef_namespaces.id'), 94 namespace_id = Column(Integer(), ForeignKey('metadef_namespaces.id'),
@@ -95,9 +102,11 @@ class MetadefObject(BASE_DICT, GlanceMetadefBase):
95class MetadefProperty(BASE_DICT, GlanceMetadefBase): 102class MetadefProperty(BASE_DICT, GlanceMetadefBase):
96 """Represents a metadata-schema namespace-property in the datastore.""" 103 """Represents a metadata-schema namespace-property in the datastore."""
97 __tablename__ = 'metadef_properties' 104 __tablename__ = 'metadef_properties'
98 __table_args__ = (Index('ix_metadef_properties_namespace_id', 105 __table_args__ = (UniqueConstraint('namespace_id', 'name',
99 'namespace_id'), 106 name='uq_metadef_properties_namespace'
100 Index('ix_metadef_properties_name', 'name')) 107 '_id_name'),
108 Index('ix_metadef_properties_name', 'name')
109 )
101 110
102 id = Column(Integer, primary_key=True, nullable=False) 111 id = Column(Integer, primary_key=True, nullable=False)
103 namespace_id = Column(Integer(), ForeignKey('metadef_namespaces.id'), 112 namespace_id = Column(Integer(), ForeignKey('metadef_namespaces.id'),
@@ -109,10 +118,9 @@ class MetadefProperty(BASE_DICT, GlanceMetadefBase):
109class MetadefNamespaceResourceType(BASE_DICT, GlanceMetadefBase): 118class MetadefNamespaceResourceType(BASE_DICT, GlanceMetadefBase):
110 """Represents a metadata-schema namespace-property in the datastore.""" 119 """Represents a metadata-schema namespace-property in the datastore."""
111 __tablename__ = 'metadef_namespace_resource_types' 120 __tablename__ = 'metadef_namespace_resource_types'
112 __table_args__ = (Index('ix_metadef_ns_res_types_res_type_id_ns_id', 121 __table_args__ = (Index('ix_metadef_ns_res_types_namespace_id',
113 'resource_type_id', 'namespace_id'), 122 'namespace_id'),
114 Index('ix_metadef_ns_res_types_namespace_id', 123 )
115 'namespace_id'))
116 124
117 resource_type_id = Column(Integer, 125 resource_type_id = Column(Integer,
118 ForeignKey('metadef_resource_types.id'), 126 ForeignKey('metadef_resource_types.id'),
@@ -126,7 +134,9 @@ class MetadefNamespaceResourceType(BASE_DICT, GlanceMetadefBase):
126class MetadefResourceType(BASE_DICT, GlanceMetadefBase): 134class MetadefResourceType(BASE_DICT, GlanceMetadefBase):
127 """Represents a metadata-schema resource type in the datastore.""" 135 """Represents a metadata-schema resource type in the datastore."""
128 __tablename__ = 'metadef_resource_types' 136 __tablename__ = 'metadef_resource_types'
129 __table_args__ = (Index('ix_metadef_resource_types_name', 'name'), ) 137 __table_args__ = (UniqueConstraint('name',
138 name='uq_metadef_resource_types_name'),
139 )
130 140
131 id = Column(Integer, primary_key=True, nullable=False) 141 id = Column(Integer, primary_key=True, nullable=False)
132 name = Column(String(80), nullable=False) 142 name = Column(String(80), nullable=False)
@@ -140,9 +150,11 @@ class MetadefResourceType(BASE_DICT, GlanceMetadefBase):
140class MetadefTag(BASE_DICT, GlanceMetadefBase): 150class MetadefTag(BASE_DICT, GlanceMetadefBase):
141 """Represents a metadata-schema tag in the data store.""" 151 """Represents a metadata-schema tag in the data store."""
142 __tablename__ = 'metadef_tags' 152 __tablename__ = 'metadef_tags'
143 __table_args__ = (Index('ix_metadef_tags_namespace_id', 153 __table_args__ = (UniqueConstraint('namespace_id', 'name',
144 'namespace_id', 'name'), 154 name='uq_metadef_tags_namespace_id'
145 Index('ix_metadef_tags_name', 'name')) 155 '_name'),
156 Index('ix_metadef_tags_name', 'name')
157 )
146 158
147 id = Column(Integer, primary_key=True, nullable=False) 159 id = Column(Integer, primary_key=True, nullable=False)
148 namespace_id = Column(Integer(), ForeignKey('metadef_namespaces.id'), 160 namespace_id = Column(Integer(), ForeignKey('metadef_namespaces.id'),
diff --git a/glance/tests/functional/db/base_metadef.py b/glance/tests/functional/db/base_metadef.py
index 986e229..87dc813 100644
--- a/glance/tests/functional/db/base_metadef.py
+++ b/glance/tests/functional/db/base_metadef.py
@@ -123,6 +123,15 @@ class MetadefNamespaceTests(object):
123 self.assertIsNotNone(created) 123 self.assertIsNotNone(created)
124 self._assert_saved_fields(fixture, created) 124 self._assert_saved_fields(fixture, created)
125 125
126 def test_namespace_create_duplicate(self):
127 fixture = build_namespace_fixture()
128 created = self.db_api.metadef_namespace_create(self.context, fixture)
129 self.assertIsNotNone(created)
130 self._assert_saved_fields(fixture, created)
131 self.assertRaises(exception.Duplicate,
132 self.db_api.metadef_namespace_create,
133 self.context, fixture)
134
126 def test_namespace_get(self): 135 def test_namespace_get(self):
127 fixture = build_namespace_fixture() 136 fixture = build_namespace_fixture()
128 created = self.db_api.metadef_namespace_create(self.context, fixture) 137 created = self.db_api.metadef_namespace_create(self.context, fixture)
@@ -222,6 +231,22 @@ class MetadefPropertyTests(object):
222 self.context, created_ns['namespace'], fixture_prop) 231 self.context, created_ns['namespace'], fixture_prop)
223 self._assert_saved_fields(fixture_prop, created_prop) 232 self._assert_saved_fields(fixture_prop, created_prop)
224 233
234 def test_property_create_duplicate(self):
235 fixture = build_namespace_fixture()
236 created_ns = self.db_api.metadef_namespace_create(
237 self.context, fixture)
238 self.assertIsNotNone(created_ns)
239 self._assert_saved_fields(fixture, created_ns)
240
241 fixture_prop = build_property_fixture(namespace_id=created_ns['id'])
242 created_prop = self.db_api.metadef_property_create(
243 self.context, created_ns['namespace'], fixture_prop)
244 self._assert_saved_fields(fixture_prop, created_prop)
245
246 self.assertRaises(exception.Duplicate,
247 self.db_api.metadef_property_create,
248 self.context, created_ns['namespace'], fixture_prop)
249
225 def test_property_get(self): 250 def test_property_get(self):
226 fixture_ns = build_namespace_fixture() 251 fixture_ns = build_namespace_fixture()
227 created_ns = self.db_api.metadef_namespace_create( 252 created_ns = self.db_api.metadef_namespace_create(
@@ -332,6 +357,23 @@ class MetadefObjectTests(object):
332 self.context, created_ns['namespace'], fixture_object) 357 self.context, created_ns['namespace'], fixture_object)
333 self._assert_saved_fields(fixture_object, created_object) 358 self._assert_saved_fields(fixture_object, created_object)
334 359
360 def test_object_create_duplicate(self):
361 fixture = build_namespace_fixture()
362 created_ns = self.db_api.metadef_namespace_create(self.context,
363 fixture)
364 self.assertIsNotNone(created_ns)
365 self._assert_saved_fields(fixture, created_ns)
366
367 fixture_object = build_object_fixture(namespace_id=created_ns['id'])
368 created_object = self.db_api.metadef_object_create(
369 self.context, created_ns['namespace'], fixture_object)
370 self._assert_saved_fields(fixture_object, created_object)
371
372 self.assertRaises(exception.Duplicate,
373 self.db_api.metadef_object_create,
374 self.context, created_ns['namespace'],
375 fixture_object)
376
335 def test_object_get(self): 377 def test_object_get(self):
336 fixture_ns = build_namespace_fixture() 378 fixture_ns = build_namespace_fixture()
337 created_ns = self.db_api.metadef_namespace_create(self.context, 379 created_ns = self.db_api.metadef_namespace_create(self.context,
@@ -442,6 +484,24 @@ class MetadefResourceTypeAssociationTests(object):
442 self.assertIsNotNone(assn_created) 484 self.assertIsNotNone(assn_created)
443 self._assert_saved_fields(assn_fixture, assn_created) 485 self._assert_saved_fields(assn_fixture, assn_created)
444 486
487 def test_association_create_duplicate(self):
488 ns_fixture = build_namespace_fixture()
489 ns_created = self.db_api.metadef_namespace_create(
490 self.context, ns_fixture)
491 self.assertIsNotNone(ns_created)
492 self._assert_saved_fields(ns_fixture, ns_created)
493
494 assn_fixture = build_association_fixture()
495 assn_created = self.db_api.metadef_resource_type_association_create(
496 self.context, ns_created['namespace'], assn_fixture)
497 self.assertIsNotNone(assn_created)
498 self._assert_saved_fields(assn_fixture, assn_created)
499
500 self.assertRaises(exception.Duplicate,
501 self.db_api.
502 metadef_resource_type_association_create,
503 self.context, ns_created['namespace'], assn_fixture)
504
445 def test_association_delete(self): 505 def test_association_delete(self):
446 ns_fixture = build_namespace_fixture() 506 ns_fixture = build_namespace_fixture()
447 ns_created = self.db_api.metadef_namespace_create( 507 ns_created = self.db_api.metadef_namespace_create(
@@ -499,6 +559,23 @@ class MetadefTagTests(object):
499 self.context, created_ns['namespace'], fixture_tag) 559 self.context, created_ns['namespace'], fixture_tag)
500 self._assert_saved_fields(fixture_tag, created_tag) 560 self._assert_saved_fields(fixture_tag, created_tag)
501 561
562 def test_tag_create_duplicate(self):
563 fixture = build_namespace_fixture()
564 created_ns = self.db_api.metadef_namespace_create(self.context,
565 fixture)
566 self.assertIsNotNone(created_ns)
567 self._assert_saved_fields(fixture, created_ns)
568
569 fixture_tag = build_tag_fixture(namespace_id=created_ns['id'])
570 created_tag = self.db_api.metadef_tag_create(
571 self.context, created_ns['namespace'], fixture_tag)
572 self._assert_saved_fields(fixture_tag, created_tag)
573
574 self.assertRaises(exception.Duplicate,
575 self.db_api.metadef_tag_create,
576 self.context, created_ns['namespace'],
577 fixture_tag)
578
502 def test_tag_create_tags(self): 579 def test_tag_create_tags(self):
503 fixture = build_namespace_fixture() 580 fixture = build_namespace_fixture()
504 created_ns = self.db_api.metadef_namespace_create(self.context, 581 created_ns = self.db_api.metadef_namespace_create(self.context,
@@ -513,6 +590,35 @@ class MetadefTagTests(object):
513 expected = set(['Tag1', 'Tag2', 'Tag3']) 590 expected = set(['Tag1', 'Tag2', 'Tag3'])
514 self.assertEqual(expected, actual) 591 self.assertEqual(expected, actual)
515 592
593 def test_tag_create_duplicate_tags_1(self):
594 fixture = build_namespace_fixture()
595 created_ns = self.db_api.metadef_namespace_create(self.context,
596 fixture)
597 self.assertIsNotNone(created_ns)
598 self._assert_saved_fields(fixture, created_ns)
599
600 tags = build_tags_fixture(['Tag1', 'Tag2', 'Tag3', 'Tag2'])
601 self.assertRaises(exception.Duplicate,
602 self.db_api.metadef_tag_create_tags,
603 self.context, created_ns['namespace'],
604 tags)
605
606 def test_tag_create_duplicate_tags_2(self):
607 fixture = build_namespace_fixture()
608 created_ns = self.db_api.metadef_namespace_create(self.context,
609 fixture)
610 self.assertIsNotNone(created_ns)
611 self._assert_saved_fields(fixture, created_ns)
612
613 tags = build_tags_fixture(['Tag1', 'Tag2', 'Tag3'])
614 self.db_api.metadef_tag_create_tags(self.context,
615 created_ns['namespace'], tags)
616 dup_tag = build_tag_fixture(namespace_id=created_ns['id'],
617 name='Tag3')
618 self.assertRaises(exception.Duplicate,
619 self.db_api.metadef_tag_create,
620 self.context, created_ns['namespace'], dup_tag)
621
516 def test_tag_get(self): 622 def test_tag_get(self):
517 fixture_ns = build_namespace_fixture() 623 fixture_ns = build_namespace_fixture()
518 created_ns = self.db_api.metadef_namespace_create(self.context, 624 created_ns = self.db_api.metadef_namespace_create(self.context,
diff --git a/glance/tests/functional/v2/test_metadef_namespaces.py b/glance/tests/functional/v2/test_metadef_namespaces.py
index f1d6de0..fbacadc 100644
--- a/glance/tests/functional/v2/test_metadef_namespaces.py
+++ b/glance/tests/functional/v2/test_metadef_namespaces.py
@@ -96,6 +96,10 @@ class TestNamespaces(functional.FunctionalTest):
96 for key, value in expected_namespace.items(): 96 for key, value in expected_namespace.items():
97 self.assertEqual(namespace[key], value, key) 97 self.assertEqual(namespace[key], value, key)
98 98
99 # Attempt to insert a duplicate
100 response = requests.post(path, headers=headers, data=data)
101 self.assertEqual(409, response.status_code)
102
99 # Get the namespace using the returned Location header 103 # Get the namespace using the returned Location header
100 response = requests.get(namespace_loc_header, headers=self._headers()) 104 response = requests.get(namespace_loc_header, headers=self._headers())
101 self.assertEqual(200, response.status_code) 105 self.assertEqual(200, response.status_code)
diff --git a/glance/tests/functional/v2/test_metadef_objects.py b/glance/tests/functional/v2/test_metadef_objects.py
index 18e15fc..8a14d0c 100644
--- a/glance/tests/functional/v2/test_metadef_objects.py
+++ b/glance/tests/functional/v2/test_metadef_objects.py
@@ -107,6 +107,10 @@ class TestMetadefObjects(functional.FunctionalTest):
107 response = requests.post(path, headers=headers, data=data) 107 response = requests.post(path, headers=headers, data=data)
108 self.assertEqual(201, response.status_code) 108 self.assertEqual(201, response.status_code)
109 109
110 # Attempt to insert a duplicate
111 response = requests.post(path, headers=headers, data=data)
112 self.assertEqual(409, response.status_code)
113
110 # Get the metadata object created above 114 # Get the metadata object created above
111 path = self._url('/v2/metadefs/namespaces/%s/objects/%s' % 115 path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
112 (namespace_name, metadata_object_name)) 116 (namespace_name, metadata_object_name))
diff --git a/glance/tests/functional/v2/test_metadef_properties.py b/glance/tests/functional/v2/test_metadef_properties.py
index d754b70..9909bd0 100644
--- a/glance/tests/functional/v2/test_metadef_properties.py
+++ b/glance/tests/functional/v2/test_metadef_properties.py
@@ -99,6 +99,10 @@ class TestNamespaceProperties(functional.FunctionalTest):
99 response = requests.post(path, headers=headers, data=data) 99 response = requests.post(path, headers=headers, data=data)
100 self.assertEqual(201, response.status_code) 100 self.assertEqual(201, response.status_code)
101 101
102 # Attempt to insert a duplicate
103 response = requests.post(path, headers=headers, data=data)
104 self.assertEqual(409, response.status_code)
105
102 # Get the property created above 106 # Get the property created above
103 path = self._url('/v2/metadefs/namespaces/%s/properties/%s' % 107 path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
104 (namespace_name, property_name)) 108 (namespace_name, property_name))
diff --git a/glance/tests/functional/v2/test_metadef_tags.py b/glance/tests/functional/v2/test_metadef_tags.py
index 3fba764..d6a75a0 100644
--- a/glance/tests/functional/v2/test_metadef_tags.py
+++ b/glance/tests/functional/v2/test_metadef_tags.py
@@ -105,6 +105,11 @@ class TestMetadefTags(functional.FunctionalTest):
105 if(key in checked_values): 105 if(key in checked_values):
106 self.assertEqual(metadata_tag[key], value, key) 106 self.assertEqual(metadata_tag[key], value, key)
107 107
108 # Try to create a duplicate metadata tag
109 headers = self._headers({'content-type': 'application/json'})
110 response = requests.post(path, headers=headers)
111 self.assertEqual(409, response.status_code)
112
108 # The metadata_tag should be mutable 113 # The metadata_tag should be mutable
109 path = self._url('/v2/metadefs/namespaces/%s/tags/%s' % 114 path = self._url('/v2/metadefs/namespaces/%s/tags/%s' %
110 (namespace_name, metadata_tag_name)) 115 (namespace_name, metadata_tag_name))
diff --git a/glance/tests/unit/test_migrations.py b/glance/tests/unit/test_migrations.py
index be70cfe..503dbc2 100644
--- a/glance/tests/unit/test_migrations.py
+++ b/glance/tests/unit/test_migrations.py
@@ -1621,6 +1621,208 @@ class MigrationsMixin(test_migrations.WalkVersionsMixin):
1621 self.assert_table(engine, 'artifact_blob_locations', locations_indices, 1621 self.assert_table(engine, 'artifact_blob_locations', locations_indices,
1622 locations_columns) 1622 locations_columns)
1623 1623
1624 def _pre_upgrade_042(self, engine):
1625 meta = sqlalchemy.MetaData()
1626 meta.bind = engine
1627
1628 metadef_namespaces = sqlalchemy.Table('metadef_namespaces', meta,
1629 autoload=True)
1630 metadef_objects = sqlalchemy.Table('metadef_objects', meta,
1631 autoload=True)
1632 metadef_properties = sqlalchemy.Table('metadef_properties', meta,
1633 autoload=True)
1634 metadef_tags = sqlalchemy.Table('metadef_tags', meta, autoload=True)
1635 metadef_resource_types = sqlalchemy.Table('metadef_resource_types',
1636 meta, autoload=True)
1637 metadef_ns_res_types = sqlalchemy.Table(
1638 'metadef_namespace_resource_types',
1639 meta, autoload=True)
1640
1641 # These will be dropped and recreated as unique constraints.
1642 self.assertTrue(index_exist('ix_metadef_namespaces_namespace',
1643 metadef_namespaces.name, engine))
1644 self.assertTrue(index_exist('ix_metadef_objects_namespace_id',
1645 metadef_objects.name, engine))
1646 self.assertTrue(index_exist('ix_metadef_properties_namespace_id',
1647 metadef_properties.name, engine))
1648 self.assertTrue(index_exist('ix_metadef_tags_namespace_id',
1649 metadef_tags.name, engine))
1650 self.assertTrue(index_exist('ix_metadef_resource_types_name',
1651 metadef_resource_types.name, engine))
1652
1653 # This one will be dropped - not needed
1654 self.assertTrue(index_exist(
1655 'ix_metadef_ns_res_types_res_type_id_ns_id',
1656 metadef_ns_res_types.name, engine))
1657
1658 # The rest must remain
1659 self.assertTrue(index_exist('ix_metadef_namespaces_owner',
1660 metadef_namespaces.name, engine))
1661 self.assertTrue(index_exist('ix_metadef_objects_name',
1662 metadef_objects.name, engine))
1663 self.assertTrue(index_exist('ix_metadef_properties_name',
1664 metadef_properties.name, engine))
1665 self.assertTrue(index_exist('ix_metadef_tags_name',
1666 metadef_tags.name, engine))
1667 self.assertTrue(index_exist('ix_metadef_ns_res_types_namespace_id',
1668 metadef_ns_res_types.name, engine))
1669
1670 # To be created
1671 self.assertFalse(unique_constraint_exist
1672 ('uq_metadef_objects_namespace_id_name',
1673 metadef_objects.name, engine)
1674 )
1675 self.assertFalse(unique_constraint_exist
1676 ('uq_metadef_properties_namespace_id_name',
1677 metadef_properties.name, engine)
1678 )
1679 self.assertFalse(unique_constraint_exist
1680 ('uq_metadef_tags_namespace_id_name',
1681 metadef_tags.name, engine)
1682 )
1683 self.assertFalse(unique_constraint_exist
1684 ('uq_metadef_namespaces_namespace',
1685 metadef_namespaces.name, engine)
1686 )
1687 self.assertFalse(unique_constraint_exist
1688 ('uq_metadef_resource_types_name',
1689 metadef_resource_types.name, engine)
1690 )
1691
1692 def _check_042(self, engine, data):
1693 meta = sqlalchemy.MetaData()
1694 meta.bind = engine
1695
1696 metadef_namespaces = sqlalchemy.Table('metadef_namespaces', meta,
1697 autoload=True)
1698 metadef_objects = sqlalchemy.Table('metadef_objects', meta,
1699 autoload=True)
1700 metadef_properties = sqlalchemy.Table('metadef_properties', meta,
1701 autoload=True)
1702 metadef_tags = sqlalchemy.Table('metadef_tags', meta, autoload=True)
1703 metadef_resource_types = sqlalchemy.Table('metadef_resource_types',
1704 meta, autoload=True)
1705 metadef_ns_res_types = sqlalchemy.Table(
1706 'metadef_namespace_resource_types',
1707 meta, autoload=True)
1708
1709 # Dropped for unique constraints
1710 self.assertFalse(index_exist('ix_metadef_namespaces_namespace',
1711 metadef_namespaces.name, engine))
1712 self.assertFalse(index_exist('ix_metadef_objects_namespace_id',
1713 metadef_objects.name, engine))
1714 self.assertFalse(index_exist('ix_metadef_properties_namespace_id',
1715 metadef_properties.name, engine))
1716 self.assertFalse(index_exist('ix_metadef_tags_namespace_id',
1717 metadef_tags.name, engine))
1718 self.assertFalse(index_exist('ix_metadef_resource_types_name',
1719 metadef_resource_types.name, engine))
1720
1721 # Dropped - not needed because of the existing primary key
1722 self.assertFalse(index_exist(
1723 'ix_metadef_ns_res_types_res_type_id_ns_id',
1724 metadef_ns_res_types.name, engine))
1725
1726 # Still exist as before
1727 self.assertTrue(index_exist('ix_metadef_namespaces_owner',
1728 metadef_namespaces.name, engine))
1729 self.assertTrue(index_exist('ix_metadef_ns_res_types_namespace_id',
1730 metadef_ns_res_types.name, engine))
1731 self.assertTrue(index_exist('ix_metadef_objects_name',
1732 metadef_objects.name, engine))
1733 self.assertTrue(index_exist('ix_metadef_properties_name',
1734 metadef_properties.name, engine))
1735 self.assertTrue(index_exist('ix_metadef_tags_name',
1736 metadef_tags.name, engine))
1737
1738 self.assertTrue(unique_constraint_exist
1739 ('uq_metadef_namespaces_namespace',
1740 metadef_namespaces.name, engine)
1741 )
1742 self.assertTrue(unique_constraint_exist
1743 ('uq_metadef_objects_namespace_id_name',
1744 metadef_objects.name, engine)
1745 )
1746 self.assertTrue(unique_constraint_exist
1747 ('uq_metadef_properties_namespace_id_name',
1748 metadef_properties.name, engine)
1749 )
1750 self.assertTrue(unique_constraint_exist
1751 ('uq_metadef_tags_namespace_id_name',
1752 metadef_tags.name, engine)
1753 )
1754 self.assertTrue(unique_constraint_exist
1755 ('uq_metadef_resource_types_name',
1756 metadef_resource_types.name, engine)
1757 )
1758
1759 def _post_downgrade_042(self, engine):
1760 meta = sqlalchemy.MetaData()
1761 meta.bind = engine
1762
1763 metadef_namespaces = sqlalchemy.Table('metadef_namespaces', meta,
1764 autoload=True)
1765 metadef_objects = sqlalchemy.Table('metadef_objects', meta,
1766 autoload=True)
1767 metadef_properties = sqlalchemy.Table('metadef_properties', meta,
1768 autoload=True)
1769 metadef_tags = sqlalchemy.Table('metadef_tags', meta, autoload=True)
1770 metadef_resource_types = sqlalchemy.Table('metadef_resource_types',
1771 meta, autoload=True)
1772 metadef_ns_res_types = sqlalchemy.Table(
1773 'metadef_namespace_resource_types',
1774 meta, autoload=True)
1775
1776 # These have been recreated
1777 self.assertTrue(index_exist('ix_metadef_namespaces_namespace',
1778 metadef_namespaces.name, engine))
1779 self.assertTrue(index_exist('ix_metadef_objects_namespace_id',
1780 metadef_objects.name, engine))
1781 self.assertTrue(index_exist('ix_metadef_properties_namespace_id',
1782 metadef_properties.name, engine))
1783 self.assertTrue(index_exist('ix_metadef_tags_namespace_id',
1784 metadef_tags.name, engine))
1785 self.assertTrue(index_exist('ix_metadef_resource_types_name',
1786 metadef_resource_types.name, engine))
1787
1788 self.assertTrue(index_exist(
1789 'ix_metadef_ns_res_types_res_type_id_ns_id',
1790 metadef_ns_res_types.name, engine))
1791
1792 # The rest must remain
1793 self.assertTrue(index_exist('ix_metadef_namespaces_owner',
1794 metadef_namespaces.name, engine))
1795 self.assertTrue(index_exist('ix_metadef_objects_name',
1796 metadef_objects.name, engine))
1797 self.assertTrue(index_exist('ix_metadef_properties_name',
1798 metadef_properties.name, engine))
1799 self.assertTrue(index_exist('ix_metadef_tags_name',
1800 metadef_tags.name, engine))
1801 self.assertTrue(index_exist('ix_metadef_ns_res_types_namespace_id',
1802 metadef_ns_res_types.name, engine))
1803
1804 # Dropped
1805 self.assertFalse(unique_constraint_exist
1806 ('uq_metadef_objects_namespace_id_name',
1807 metadef_objects.name, engine)
1808 )
1809 self.assertFalse(unique_constraint_exist
1810 ('uq_metadef_properties_namespace_id_name',
1811 metadef_properties.name, engine)
1812 )
1813 self.assertFalse(unique_constraint_exist
1814 ('uq_metadef_tags_namespace_id_name',
1815 metadef_tags.name, engine)
1816 )
1817 self.assertFalse(unique_constraint_exist
1818 ('uq_metadef_namespaces_namespace',
1819 metadef_namespaces.name, engine)
1820 )
1821 self.assertFalse(unique_constraint_exist
1822 ('uq_metadef_resource_types_name',
1823 metadef_resource_types.name, engine)
1824 )
1825
1624 def assert_table(self, engine, table_name, indices, columns): 1826 def assert_table(self, engine, table_name, indices, columns):
1625 table = db_utils.get_table(engine, table_name) 1827 table = db_utils.get_table(engine, table_name)
1626 index_data = [(index.name, index.columns.keys()) for index in 1828 index_data = [(index.name, index.columns.keys()) for index in
diff --git a/glance/tests/unit/v2/test_metadef_resources.py b/glance/tests/unit/v2/test_metadef_resources.py
index 0751d1b..bb68f3b 100644
--- a/glance/tests/unit/v2/test_metadef_resources.py
+++ b/glance/tests/unit/v2/test_metadef_resources.py
@@ -605,6 +605,17 @@ class TestMetadefsControllers(base.IsolatedUnitTest):
605 namespace = self.namespace_controller.show(request, NAMESPACE4) 605 namespace = self.namespace_controller.show(request, NAMESPACE4)
606 self.assertEqual(NAMESPACE4, namespace.namespace) 606 self.assertEqual(NAMESPACE4, namespace.namespace)
607 607
608 def test_namespace_create_duplicate(self):
609 request = unit_test_utils.get_fake_request()
610
611 namespace = namespaces.Namespace()
612 namespace.namespace = 'new-namespace'
613 new_ns = self.namespace_controller.create(request, namespace)
614 self.assertEqual('new-namespace', new_ns.namespace)
615 self.assertRaises(webob.exc.HTTPConflict,
616 self.namespace_controller.create,
617 request, namespace)
618
608 def test_namespace_create_different_owner(self): 619 def test_namespace_create_different_owner(self):
609 request = unit_test_utils.get_fake_request() 620 request = unit_test_utils.get_fake_request()
610 621
@@ -1036,6 +1047,20 @@ class TestMetadefsControllers(base.IsolatedUnitTest):
1036 property) 1047 property)
1037 self.assertNotificationsLog([]) 1048 self.assertNotificationsLog([])
1038 1049
1050 def test_property_create_duplicate(self):
1051 request = unit_test_utils.get_fake_request()
1052
1053 property = properties.PropertyType()
1054 property.name = 'new-property'
1055 property.type = 'string'
1056 property.title = 'title'
1057 new_property = self.property_controller.create(request, NAMESPACE1,
1058 property)
1059 self.assertEqual('new-property', new_property.name)
1060 self.assertRaises(webob.exc.HTTPConflict,
1061 self.property_controller.create, request,
1062 NAMESPACE1, property)
1063
1039 def test_property_update(self): 1064 def test_property_update(self):
1040 request = unit_test_utils.get_fake_request(tenant=TENANT3) 1065 request = unit_test_utils.get_fake_request(tenant=TENANT3)
1041 1066
@@ -1254,6 +1279,19 @@ class TestMetadefsControllers(base.IsolatedUnitTest):
1254 self.assertEqual([], object.required) 1279 self.assertEqual([], object.required)
1255 self.assertEqual({}, object.properties) 1280 self.assertEqual({}, object.properties)
1256 1281
1282 def test_object_create_duplicate(self):
1283 request = unit_test_utils.get_fake_request()
1284
1285 object = objects.MetadefObject()
1286 object.name = 'New-Object'
1287 object.required = []
1288 object.properties = {}
1289 new_obj = self.object_controller.create(request, object, NAMESPACE3)
1290 self.assertEqual('New-Object', new_obj.name)
1291 self.assertRaises(webob.exc.HTTPConflict,
1292 self.object_controller.create, request, object,
1293 NAMESPACE3)
1294
1257 def test_object_create_conflict(self): 1295 def test_object_create_conflict(self):
1258 request = unit_test_utils.get_fake_request() 1296 request = unit_test_utils.get_fake_request()
1259 1297