Merge "Add extra data to Stack table for Convergence."

This commit is contained in:
Jenkins 2015-03-06 02:58:05 +00:00 committed by Gerrit Code Review
commit 7f0ff06247
7 changed files with 179 additions and 10 deletions

View File

@ -416,14 +416,21 @@ def stack_create(context, values):
def stack_update(context, stack_id, values): def stack_update(context, stack_id, values):
stack = stack_get(context, stack_id) stack = stack_get(context, stack_id)
if not stack: if stack is None:
raise exception.NotFound(_('Attempt to update a stack with id: ' raise exception.NotFound(_('Attempt to update a stack with id: '
'%(id)s %(msg)s') % { '%(id)s %(msg)s') % {
'id': stack_id, 'id': stack_id,
'msg': 'that does not exist'}) 'msg': 'that does not exist'})
stack.update(values) session = _session(context)
stack.save(_session(context)) rows_updated = (session.query(models.Stack)
.filter(models.Stack.id == stack.id)
.filter(models.Stack.current_traversal
== stack.current_traversal)
.update(values, synchronize_session=False))
session.expire_all()
return (rows_updated is not None and rows_updated > 0)
def stack_delete(context, stack_id): def stack_delete(context, stack_id):

View File

@ -0,0 +1,140 @@
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sqlalchemy
from heat.db.sqlalchemy import types as heat_db_types
from heat.db.sqlalchemy import utils as migrate_utils
from migrate import ForeignKeyConstraint
def upgrade(migrate_engine):
if migrate_engine.name == 'sqlite':
_upgrade_sqlite(migrate_engine)
return
meta = sqlalchemy.MetaData(bind=migrate_engine)
stack = sqlalchemy.Table('stack', meta, autoload=True)
prev_raw_template_id = sqlalchemy.Column('prev_raw_template_id',
sqlalchemy.Integer)
current_traversal = sqlalchemy.Column('current_traversal',
sqlalchemy.String(36))
current_deps = sqlalchemy.Column('current_deps', heat_db_types.Json)
prev_raw_template_id.create(stack)
current_traversal.create(stack)
current_deps.create(stack)
raw_template = sqlalchemy.Table('raw_template', meta, autoload=True)
fkey = ForeignKeyConstraint(columns=[stack.c.prev_raw_template_id],
refcolumns=[raw_template.c.id],
name='prev_raw_template_ref')
fkey.create()
def _upgrade_sqlite(migrate_engine):
meta = sqlalchemy.MetaData(bind=migrate_engine)
stack = sqlalchemy.Table('stack', meta, autoload=True)
table_name = stack.name
newcols = [
sqlalchemy.Column('prev_raw_template_id', sqlalchemy.Integer,
sqlalchemy.ForeignKey('raw_template.id',
name='prev_raw_template_ref')),
sqlalchemy.Column('current_traversal', sqlalchemy.String(36)),
sqlalchemy.Column('current_deps', heat_db_types.Json),
]
new_stack = migrate_utils.clone_table(table_name + '__tmp__', stack,
meta, newcols=newcols)
# migrate stacks into new table
stacks = list(stack.select().order_by(
sqlalchemy.sql.expression.asc(stack.c.created_at))
.execute())
colnames = [c.name for c in stack.columns]
for s in stacks:
values = dict(zip(colnames,
map(lambda colname: getattr(s, colname),
colnames)))
migrate_engine.execute(new_stack.insert(values))
# Drop old tables and rename new ones
stack.drop()
new_stack.rename('stack')
# add the indexes back
_add_indexes(migrate_engine, new_stack)
def downgrade(migrate_engine):
meta = sqlalchemy.MetaData(bind=migrate_engine)
stack = sqlalchemy.Table('stack', meta, autoload=True)
if migrate_engine.name == 'sqlite':
_downgrade_sqlite(migrate_engine)
else:
raw_template = sqlalchemy.Table('raw_template', meta, autoload=True)
fkey = ForeignKeyConstraint(columns=[stack.c.prev_raw_template_id],
refcolumns=[raw_template.c.id],
name='prev_raw_template_ref')
fkey.drop()
stack.c.prev_raw_template_id.drop()
stack.c.current_traversal.drop()
stack.c.current_deps.drop()
def _downgrade_sqlite(migrate_engine):
meta = sqlalchemy.MetaData(bind=migrate_engine)
stack = sqlalchemy.Table('stack', meta, autoload=True)
table_name = stack.name
# ignore CheckConstraints and FK Constraint on prev_raw_template_id.
ignorecols = [
stack.c.prev_raw_template_id.name,
stack.c.current_traversal.name,
stack.c.current_deps.name,
]
ignorecons = [
'prev_raw_template_ref',
]
new_stack = migrate_utils.clone_table(table_name + '__tmp__', stack, meta,
ignorecols=ignorecols,
ignorecons=ignorecons)
migrate_data = """
INSERT INTO %s
SELECT id, created_at, updated_at, name, raw_template_id,
user_creds_id, username, owner_id, status, status_reason,
parameters, timeout, tenant, disable_rollback, action,
deleted_at, stack_user_project_id, backup, nested_depth,
convergence
FROM stack;""" % new_stack.name
migrate_engine.execute(migrate_data)
stack.drop()
new_stack.rename(table_name)
# add the indexes back to new table
_add_indexes(migrate_engine, new_stack)
def _add_indexes(migrate_engine, stack):
name_index = sqlalchemy.Index('ix_stack_name',
stack.c.name,
mysql_length=255)
tenant_index = sqlalchemy.Index('ix_stack_tenant',
stack.c.tenant,
mysql_length=255)
name_index.create(migrate_engine)
tenant_index.create(migrate_engine)

View File

@ -141,7 +141,14 @@ class Stack(BASE, HeatBase, SoftDelete, StateAware):
sqlalchemy.Integer, sqlalchemy.Integer,
sqlalchemy.ForeignKey('raw_template.id'), sqlalchemy.ForeignKey('raw_template.id'),
nullable=False) nullable=False)
raw_template = relationship(RawTemplate, backref=backref('stack')) raw_template = relationship(RawTemplate, backref=backref('stack'),
foreign_keys=[raw_template_id])
prev_raw_template_id = sqlalchemy.Column(
'prev_raw_template_id',
sqlalchemy.Integer,
sqlalchemy.ForeignKey('raw_template.id'))
prev_raw_template = relationship(RawTemplate,
foreign_keys=[prev_raw_template_id])
username = sqlalchemy.Column(sqlalchemy.String(256)) username = sqlalchemy.Column(sqlalchemy.String(256))
tenant = sqlalchemy.Column(sqlalchemy.String(256)) tenant = sqlalchemy.Column(sqlalchemy.String(256))
parameters = sqlalchemy.Column('parameters', types.Json) parameters = sqlalchemy.Column('parameters', types.Json)
@ -158,6 +165,9 @@ class Stack(BASE, HeatBase, SoftDelete, StateAware):
convergence = sqlalchemy.Column('convergence', sqlalchemy.Boolean) convergence = sqlalchemy.Column('convergence', sqlalchemy.Boolean)
tags = relationship(StackTag, cascade="all,delete", tags = relationship(StackTag, cascade="all,delete",
backref=backref('stack')) backref=backref('stack'))
current_traversal = sqlalchemy.Column('current_traversal',
sqlalchemy.String(36))
current_deps = sqlalchemy.Column('current_deps', types.Json)
# Override timestamp column to store the correct value: it should be the # Override timestamp column to store the correct value: it should be the
# time the create/update call was issued, not the time the DB entry is # time the create/update call was issued, not the time the DB entry is

View File

@ -79,7 +79,8 @@ class Stack(collections.Mapping):
created_time=None, updated_time=None, created_time=None, updated_time=None,
user_creds_id=None, tenant_id=None, user_creds_id=None, tenant_id=None,
use_stored_context=False, username=None, use_stored_context=False, username=None,
nested_depth=0, strict_validate=True, convergence=False): nested_depth=0, strict_validate=True, convergence=False,
current_traversal=None):
''' '''
Initialise from a context, name, Template object and (optionally) Initialise from a context, name, Template object and (optionally)
Environment object. The database ID may also be initialised, if the Environment object. The database ID may also be initialised, if the
@ -121,6 +122,7 @@ class Stack(collections.Mapping):
self.nested_depth = nested_depth self.nested_depth = nested_depth
self.strict_validate = strict_validate self.strict_validate = strict_validate
self.convergence = convergence self.convergence = convergence
self.current_traversal = current_traversal
if use_stored_context: if use_stored_context:
self.context = self.stored_context() self.context = self.stored_context()
@ -322,13 +324,14 @@ class Stack(collections.Mapping):
updated_time=stack.updated_at, updated_time=stack.updated_at,
user_creds_id=stack.user_creds_id, tenant_id=stack.tenant, user_creds_id=stack.user_creds_id, tenant_id=stack.tenant,
use_stored_context=use_stored_context, use_stored_context=use_stored_context,
username=stack.username, convergence=stack.convergence) username=stack.username, convergence=stack.convergence,
current_traversal=stack.current_traversal)
@profiler.trace('Stack.store', hide_args=False) @profiler.trace('Stack.store', hide_args=False)
def store(self, backup=False): def store(self, backup=False):
''' '''
Store the stack in the database and return its ID Store the stack in the database and return its ID
If self.id is set, we update the existing stack If self.id is set, we update the existing stack.
''' '''
s = { s = {
'name': self._backup_name() if backup else self.name, 'name': self._backup_name() if backup else self.name,
@ -347,7 +350,8 @@ class Stack(collections.Mapping):
'user_creds_id': self.user_creds_id, 'user_creds_id': self.user_creds_id,
'backup': backup, 'backup': backup,
'nested_depth': self.nested_depth, 'nested_depth': self.nested_depth,
'convergence': self.convergence 'convergence': self.convergence,
'current_traversal': self.current_traversal,
} }
if self.id: if self.id:
db_api.stack_update(self.context, self.id, s) db_api.stack_update(self.context, self.id, s)

View File

@ -403,6 +403,11 @@ class HeatMigrationsCheckers(test_migrations.WalkVersionsMixin,
def _check_052(self, engine, data): def _check_052(self, engine, data):
self.assertColumnExists(engine, 'stack', 'convergence') self.assertColumnExists(engine, 'stack', 'convergence')
def _check_055(self, engine, data):
self.assertColumnExists(engine, 'stack', 'prev_raw_template_id')
self.assertColumnExists(engine, 'stack', 'current_traversal')
self.assertColumnExists(engine, 'stack', 'current_deps')
class TestHeatMigrationsMySQL(HeatMigrationsCheckers, class TestHeatMigrationsMySQL(HeatMigrationsCheckers,
test_base.MySQLOpportunisticTestCase): test_base.MySQLOpportunisticTestCase):

View File

@ -1153,7 +1153,8 @@ class StackTest(common.HeatTestCase):
tenant_id='test_tenant_id', tenant_id='test_tenant_id',
use_stored_context=False, use_stored_context=False,
username=mox.IgnoreArg(), username=mox.IgnoreArg(),
convergence=False) convergence=False,
current_traversal=None)
self.m.ReplayAll() self.m.ReplayAll()
parser.Stack.load(self.ctx, stack_id=self.stack.id, parser.Stack.load(self.ctx, stack_id=self.stack.id,

View File

@ -1366,6 +1366,7 @@ class DBAPIStackTest(common.HeatTestCase):
'status': 'failed', 'status': 'failed',
'status_reason': "update_failed", 'status_reason': "update_failed",
'timeout': '90', 'timeout': '90',
'current_traversal': 'dummy-uuid',
} }
db_api.stack_update(self.ctx, stack.id, values) db_api.stack_update(self.ctx, stack.id, values)
stack = db_api.stack_get(self.ctx, stack.id) stack = db_api.stack_get(self.ctx, stack.id)
@ -1373,7 +1374,8 @@ class DBAPIStackTest(common.HeatTestCase):
self.assertEqual('update', stack.action) self.assertEqual('update', stack.action)
self.assertEqual('failed', stack.status) self.assertEqual('failed', stack.status)
self.assertEqual('update_failed', stack.status_reason) self.assertEqual('update_failed', stack.status_reason)
self.assertEqual('90', stack.timeout) self.assertEqual(90, stack.timeout)
self.assertEqual('dummy-uuid', stack.current_traversal)
self.assertRaises(exception.NotFound, db_api.stack_update, self.ctx, self.assertRaises(exception.NotFound, db_api.stack_update, self.ctx,
UUID2, values) UUID2, values)