Browse Source

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
tags/11.0.0.0rc1
Wayne Okuma 4 years ago
parent
commit
5369e86e8d

+ 604
- 0
glance/db/sqlalchemy/migrate_repo/versions/042_add_changes_to_reinstall_unique_metadef_constraints.py View File

@@ -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
+
14
+import migrate
15
+import sqlalchemy
16
+from 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.
24
+def _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
+
41
+def _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
+
60
+def _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
+
79
+def _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
+
98
+def _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
+
116
+def _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
+
220
+def _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
+
234
+def _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
+
255
+def _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
+
265
+def _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
+
284
+def _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
+
302
+def _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
+
310
+def _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
+
318
+def 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
+
484
+def 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()

+ 27
- 15
glance/db/sqlalchemy/models_metadef.py View File

@@ -28,6 +28,7 @@ from sqlalchemy import Integer
28 28
 from sqlalchemy.orm import relationship
29 29
 from sqlalchemy import String
30 30
 from sqlalchemy import Text
31
+from sqlalchemy import UniqueConstraint
31 32
 
32 33
 from glance.db.sqlalchemy.models import JSONEncodedDict
33 34
 
@@ -65,8 +66,11 @@ class GlanceMetadefBase(models.TimestampMixin):
65 66
 class MetadefNamespace(BASE_DICT, GlanceMetadefBase):
66 67
     """Represents a metadata-schema namespace in the datastore."""
67 68
     __tablename__ = 'metadef_namespaces'
68
-    __table_args__ = (Index('ix_metadef_namespaces_namespace', 'namespace'),
69
-                      Index('ix_metadef_namespaces_owner', 'owner'))
69
+    __table_args__ = (UniqueConstraint('namespace',
70
+                                       name='uq_metadef_namespaces'
71
+                                            '_namespace'),
72
+                      Index('ix_metadef_namespaces_owner', 'owner')
73
+                      )
70 74
 
71 75
     id = Column(Integer, primary_key=True, nullable=False)
72 76
     namespace = Column(String(80), nullable=False)
@@ -80,8 +84,11 @@ class MetadefNamespace(BASE_DICT, GlanceMetadefBase):
80 84
 class MetadefObject(BASE_DICT, GlanceMetadefBase):
81 85
     """Represents a metadata-schema object in the datastore."""
82 86
     __tablename__ = 'metadef_objects'
83
-    __table_args__ = (Index('ix_metadef_objects_namespace_id', 'namespace_id'),
84
-                      Index('ix_metadef_objects_name', 'name'))
87
+    __table_args__ = (UniqueConstraint('namespace_id', 'name',
88
+                                       name='uq_metadef_objects_namespace_id'
89
+                                            '_name'),
90
+                      Index('ix_metadef_objects_name', 'name')
91
+                      )
85 92
 
86 93
     id = Column(Integer, primary_key=True, nullable=False)
87 94
     namespace_id = Column(Integer(), ForeignKey('metadef_namespaces.id'),
@@ -95,9 +102,11 @@ class MetadefObject(BASE_DICT, GlanceMetadefBase):
95 102
 class MetadefProperty(BASE_DICT, GlanceMetadefBase):
96 103
     """Represents a metadata-schema namespace-property in the datastore."""
97 104
     __tablename__ = 'metadef_properties'
98
-    __table_args__ = (Index('ix_metadef_properties_namespace_id',
99
-                            'namespace_id'),
100
-                      Index('ix_metadef_properties_name', 'name'))
105
+    __table_args__ = (UniqueConstraint('namespace_id', 'name',
106
+                                       name='uq_metadef_properties_namespace'
107
+                                            '_id_name'),
108
+                      Index('ix_metadef_properties_name', 'name')
109
+                      )
101 110
 
102 111
     id = Column(Integer, primary_key=True, nullable=False)
103 112
     namespace_id = Column(Integer(), ForeignKey('metadef_namespaces.id'),
@@ -109,10 +118,9 @@ class MetadefProperty(BASE_DICT, GlanceMetadefBase):
109 118
 class MetadefNamespaceResourceType(BASE_DICT, GlanceMetadefBase):
110 119
     """Represents a metadata-schema namespace-property in the datastore."""
111 120
     __tablename__ = 'metadef_namespace_resource_types'
112
-    __table_args__ = (Index('ix_metadef_ns_res_types_res_type_id_ns_id',
113
-                            'resource_type_id', 'namespace_id'),
114
-                      Index('ix_metadef_ns_res_types_namespace_id',
115
-                            'namespace_id'))
121
+    __table_args__ = (Index('ix_metadef_ns_res_types_namespace_id',
122
+                            'namespace_id'),
123
+                      )
116 124
 
117 125
     resource_type_id = Column(Integer,
118 126
                               ForeignKey('metadef_resource_types.id'),
@@ -126,7 +134,9 @@ class MetadefNamespaceResourceType(BASE_DICT, GlanceMetadefBase):
126 134
 class MetadefResourceType(BASE_DICT, GlanceMetadefBase):
127 135
     """Represents a metadata-schema resource type in the datastore."""
128 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 141
     id = Column(Integer, primary_key=True, nullable=False)
132 142
     name = Column(String(80), nullable=False)
@@ -140,9 +150,11 @@ class MetadefResourceType(BASE_DICT, GlanceMetadefBase):
140 150
 class MetadefTag(BASE_DICT, GlanceMetadefBase):
141 151
     """Represents a metadata-schema tag in the data store."""
142 152
     __tablename__ = 'metadef_tags'
143
-    __table_args__ = (Index('ix_metadef_tags_namespace_id',
144
-                            'namespace_id', 'name'),
145
-                      Index('ix_metadef_tags_name', 'name'))
153
+    __table_args__ = (UniqueConstraint('namespace_id', 'name',
154
+                                       name='uq_metadef_tags_namespace_id'
155
+                                            '_name'),
156
+                      Index('ix_metadef_tags_name', 'name')
157
+                      )
146 158
 
147 159
     id = Column(Integer, primary_key=True, nullable=False)
148 160
     namespace_id = Column(Integer(), ForeignKey('metadef_namespaces.id'),

+ 106
- 0
glance/tests/functional/db/base_metadef.py View File

@@ -123,6 +123,15 @@ class MetadefNamespaceTests(object):
123 123
         self.assertIsNotNone(created)
124 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 135
     def test_namespace_get(self):
127 136
         fixture = build_namespace_fixture()
128 137
         created = self.db_api.metadef_namespace_create(self.context, fixture)
@@ -222,6 +231,22 @@ class MetadefPropertyTests(object):
222 231
             self.context, created_ns['namespace'], fixture_prop)
223 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 250
     def test_property_get(self):
226 251
         fixture_ns = build_namespace_fixture()
227 252
         created_ns = self.db_api.metadef_namespace_create(
@@ -332,6 +357,23 @@ class MetadefObjectTests(object):
332 357
             self.context, created_ns['namespace'], fixture_object)
333 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 377
     def test_object_get(self):
336 378
         fixture_ns = build_namespace_fixture()
337 379
         created_ns = self.db_api.metadef_namespace_create(self.context,
@@ -442,6 +484,24 @@ class MetadefResourceTypeAssociationTests(object):
442 484
         self.assertIsNotNone(assn_created)
443 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 505
     def test_association_delete(self):
446 506
         ns_fixture = build_namespace_fixture()
447 507
         ns_created = self.db_api.metadef_namespace_create(
@@ -499,6 +559,23 @@ class MetadefTagTests(object):
499 559
             self.context, created_ns['namespace'], fixture_tag)
500 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 579
     def test_tag_create_tags(self):
503 580
         fixture = build_namespace_fixture()
504 581
         created_ns = self.db_api.metadef_namespace_create(self.context,
@@ -513,6 +590,35 @@ class MetadefTagTests(object):
513 590
         expected = set(['Tag1', 'Tag2', 'Tag3'])
514 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 622
     def test_tag_get(self):
517 623
         fixture_ns = build_namespace_fixture()
518 624
         created_ns = self.db_api.metadef_namespace_create(self.context,

+ 4
- 0
glance/tests/functional/v2/test_metadef_namespaces.py View File

@@ -96,6 +96,10 @@ class TestNamespaces(functional.FunctionalTest):
96 96
         for key, value in expected_namespace.items():
97 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 103
         # Get the namespace using the returned Location header
100 104
         response = requests.get(namespace_loc_header, headers=self._headers())
101 105
         self.assertEqual(200, response.status_code)

+ 4
- 0
glance/tests/functional/v2/test_metadef_objects.py View File

@@ -107,6 +107,10 @@ class TestMetadefObjects(functional.FunctionalTest):
107 107
         response = requests.post(path, headers=headers, data=data)
108 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 114
         # Get the metadata object created above
111 115
         path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
112 116
                          (namespace_name, metadata_object_name))

+ 4
- 0
glance/tests/functional/v2/test_metadef_properties.py View File

@@ -99,6 +99,10 @@ class TestNamespaceProperties(functional.FunctionalTest):
99 99
         response = requests.post(path, headers=headers, data=data)
100 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 106
         # Get the property created above
103 107
         path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
104 108
                          (namespace_name, property_name))

+ 5
- 0
glance/tests/functional/v2/test_metadef_tags.py View File

@@ -105,6 +105,11 @@ class TestMetadefTags(functional.FunctionalTest):
105 105
             if(key in checked_values):
106 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 113
         # The metadata_tag should be mutable
109 114
         path = self._url('/v2/metadefs/namespaces/%s/tags/%s' %
110 115
                          (namespace_name, metadata_tag_name))

+ 202
- 0
glance/tests/unit/test_migrations.py View File

@@ -1621,6 +1621,208 @@ class MigrationsMixin(test_migrations.WalkVersionsMixin):
1621 1621
         self.assert_table(engine, 'artifact_blob_locations', locations_indices,
1622 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 1826
     def assert_table(self, engine, table_name, indices, columns):
1625 1827
         table = db_utils.get_table(engine, table_name)
1626 1828
         index_data = [(index.name, index.columns.keys()) for index in

+ 38
- 0
glance/tests/unit/v2/test_metadef_resources.py View File

@@ -605,6 +605,17 @@ class TestMetadefsControllers(base.IsolatedUnitTest):
605 605
         namespace = self.namespace_controller.show(request, NAMESPACE4)
606 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 619
     def test_namespace_create_different_owner(self):
609 620
         request = unit_test_utils.get_fake_request()
610 621
 
@@ -1036,6 +1047,20 @@ class TestMetadefsControllers(base.IsolatedUnitTest):
1036 1047
                           property)
1037 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 1064
     def test_property_update(self):
1040 1065
         request = unit_test_utils.get_fake_request(tenant=TENANT3)
1041 1066
 
@@ -1254,6 +1279,19 @@ class TestMetadefsControllers(base.IsolatedUnitTest):
1254 1279
         self.assertEqual([], object.required)
1255 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 1295
     def test_object_create_conflict(self):
1258 1296
         request = unit_test_utils.get_fake_request()
1259 1297
 

Loading…
Cancel
Save