1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright 2011 OpenStack LLC.
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6
# not use this file except in compliance with the License. You may obtain
7
# a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
# License for the specific language governing permissions and limitations
17
from sqlalchemy import Boolean, Column, DateTime, Integer
18
from sqlalchemy import MetaData, String, Table
20
from nova import utils
32
def old_style_quotas_table(meta, name):
33
return Table(name, meta,
34
Column('id', Integer(), primary_key=True),
35
Column('created_at', DateTime(),
36
default=utils.utcnow),
37
Column('updated_at', DateTime(),
38
onupdate=utils.utcnow),
39
Column('deleted_at', DateTime()),
40
Column('deleted', Boolean(), default=False),
42
String(length=255, convert_unicode=False,
43
assert_unicode=None, unicode_error=None,
44
_warn_on_bytestring=False)),
45
Column('instances', Integer()),
46
Column('cores', Integer()),
47
Column('volumes', Integer()),
48
Column('gigabytes', Integer()),
49
Column('floating_ips', Integer()),
50
Column('metadata_items', Integer()),
54
def new_style_quotas_table(meta, name):
55
return Table(name, meta,
56
Column('id', Integer(), primary_key=True),
57
Column('created_at', DateTime(),
58
default=utils.utcnow),
59
Column('updated_at', DateTime(),
60
onupdate=utils.utcnow),
61
Column('deleted_at', DateTime()),
62
Column('deleted', Boolean(), default=False),
64
String(length=255, convert_unicode=False,
65
assert_unicode=None, unicode_error=None,
66
_warn_on_bytestring=False)),
68
String(length=255, convert_unicode=False,
69
assert_unicode=None, unicode_error=None,
70
_warn_on_bytestring=False),
72
Column('hard_limit', Integer(), nullable=True),
76
def quotas_table(meta, name='quotas'):
77
return Table(name, meta, autoload=True)
80
def _assert_no_duplicate_project_ids(quotas):
82
message = ('There are multiple active quotas for project "%s" '
83
'(among others, possibly). '
84
'Please resolve all ambiguous quotas before '
85
'reattempting the migration.')
87
assert quota.project_id not in project_ids, message % quota.project_id
88
project_ids.add(quota.project_id)
91
def assert_old_quotas_have_no_active_duplicates(migrate_engine, quotas):
92
"""Ensure that there are no duplicate non-deleted quota entries."""
93
select = quotas.select().where(quotas.c.deleted == False)
94
results = migrate_engine.execute(select)
95
_assert_no_duplicate_project_ids(list(results))
98
def assert_new_quotas_have_no_active_duplicates(migrate_engine, quotas):
99
"""Ensure that there are no duplicate non-deleted quota entries."""
100
for resource in resources:
101
select = quotas.select().\
102
where(quotas.c.deleted == False).\
103
where(quotas.c.resource == resource)
104
results = migrate_engine.execute(select)
105
_assert_no_duplicate_project_ids(list(results))
108
def convert_forward(migrate_engine, old_quotas, new_quotas):
109
quotas = list(migrate_engine.execute(old_quotas.select()))
111
for resource in resources:
112
hard_limit = getattr(quota, resource)
113
if hard_limit is None:
115
insert = new_quotas.insert().values(
116
created_at=quota.created_at,
117
updated_at=quota.updated_at,
118
deleted_at=quota.deleted_at,
119
deleted=quota.deleted,
120
project_id=quota.project_id,
122
hard_limit=hard_limit)
123
migrate_engine.execute(insert)
126
def earliest(date1, date2):
127
if date1 is None and date2 is None:
138
def latest(date1, date2):
139
if date1 is None and date2 is None:
150
def convert_backward(migrate_engine, old_quotas, new_quotas):
152
for quota in migrate_engine.execute(new_quotas.select()):
153
if (quota.resource not in resources
154
or quota.hard_limit is None or quota.deleted):
156
if not quota.project_id in quotas:
157
quotas[quota.project_id] = {
158
'project_id': quota.project_id,
159
'created_at': quota.created_at,
160
'updated_at': quota.updated_at,
161
quota.resource: quota.hard_limit,
164
quotas[quota.project_id]['created_at'] = earliest(
165
quota.created_at, quotas[quota.project_id]['created_at'])
166
quotas[quota.project_id]['updated_at'] = latest(
167
quota.updated_at, quotas[quota.project_id]['updated_at'])
168
quotas[quota.project_id][quota.resource] = quota.hard_limit
170
for quota in quotas.itervalues():
171
insert = old_quotas.insert().values(**quota)
172
migrate_engine.execute(insert)
175
def upgrade(migrate_engine):
176
# Upgrade operations go here. Don't create your own engine;
177
# bind migrate_engine to your metadata
179
meta.bind = migrate_engine
181
old_quotas = quotas_table(meta)
182
assert_old_quotas_have_no_active_duplicates(migrate_engine, old_quotas)
184
new_quotas = new_style_quotas_table(meta, 'quotas_new')
186
convert_forward(migrate_engine, old_quotas, new_quotas)
189
# clear metadata to work around this:
190
# http://code.google.com/p/sqlalchemy-migrate/issues/detail?id=128
192
new_quotas = quotas_table(meta, 'quotas_new')
193
new_quotas.rename('quotas')
196
def downgrade(migrate_engine):
197
# Operations to reverse the above upgrade go here.
199
meta.bind = migrate_engine
201
new_quotas = quotas_table(meta)
202
assert_new_quotas_have_no_active_duplicates(migrate_engine, new_quotas)
204
old_quotas = old_style_quotas_table(meta, 'quotas_old')
206
convert_backward(migrate_engine, old_quotas, new_quotas)
209
# clear metadata to work around this:
210
# http://code.google.com/p/sqlalchemy-migrate/issues/detail?id=128
212
old_quotas = quotas_table(meta, 'quotas_old')
213
old_quotas.rename('quotas')