Skip to content

Commit ae2a740

Browse files
fwieseljoker-at-work
authored andcommitted
Fix quota double-spending with uniqueness-constraint
We introduce a uniqueness-constraint to avoid duplicate entries in the quota_usages table. For introducing this, we have to delete the duplicates already present and the soft-deleted reservations referencing duplicate entries. Things learned here: Do not use _and_ statement, as it will replace it with an expression-object. Also we first tried a rank query, but now use a join.
1 parent d20a6c7 commit ae2a740

File tree

1 file changed

+77
-0
lines changed

1 file changed

+77
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Copyright 2018 SAP SE
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
from migrate.changeset.constraint import UniqueConstraint
16+
from sqlalchemy import MetaData, Table
17+
from sqlalchemy import select, join, and_
18+
from oslo_log import log as logging
19+
20+
LOG = logging.getLogger(__name__)
21+
META = MetaData()
22+
23+
24+
def quota_usages_table(migrate_engine):
25+
global META
26+
META.bind = migrate_engine
27+
28+
return Table('quota_usages', META, autoload=True)
29+
30+
31+
def _build_constraint(migrate_engine, quota_usages=None):
32+
if quota_usages is None:
33+
quota_usages = quota_usages_table(migrate_engine)
34+
return UniqueConstraint(
35+
'project_id', 'resource', 'deleted',
36+
table=quota_usages,
37+
)
38+
39+
40+
def upgrade(migrate_engine):
41+
global META
42+
quota_usages = quota_usages_table(migrate_engine)
43+
44+
qu1 = quota_usages.alias('qu1')
45+
qu2 = quota_usages.alias('qu2')
46+
duplicates = select([qu1.c.id]).select_from(join(
47+
qu1, qu2,
48+
onclause=and_(
49+
qu1.c.project_id == qu2.c.project_id,
50+
qu1.c.resource == qu2.c.resource,
51+
qu1.c.deleted == qu2.c.deleted,
52+
)
53+
)).where(
54+
qu1.c.id > qu2.c.id
55+
)
56+
57+
reservations = Table('reservations', META, autoload=True)
58+
59+
query = reservations.delete(
60+
whereclause=and_(reservations.c.usage_id.in_(duplicates),
61+
reservations.c.deleted)
62+
)
63+
64+
migrate_engine.execute(query)
65+
66+
query = quota_usages.delete(
67+
whereclause=quota_usages.c.id.in_(duplicates)
68+
)
69+
70+
migrate_engine.execute(query)
71+
cons = _build_constraint(migrate_engine, quota_usages=quota_usages)
72+
cons.create()
73+
74+
75+
def downgrade(migrate_engine):
76+
cons = _build_constraint(migrate_engine)
77+
cons.drop()

0 commit comments

Comments
 (0)