Merge "Change Stack timestamps to save correct info"
This commit is contained in:
commit
24682b4ca9
|
@ -191,7 +191,6 @@ class StackController(object):
|
|||
engine_api.STACK_CREATION_TIME: 'CreationTime',
|
||||
engine_api.STACK_DESCRIPTION: 'Description',
|
||||
engine_api.STACK_DISABLE_ROLLBACK: 'DisableRollback',
|
||||
engine_api.STACK_UPDATED_TIME: 'LastUpdatedTime',
|
||||
engine_api.STACK_NOTIFICATION_TOPICS: 'NotificationARNs',
|
||||
engine_api.STACK_PARAMETERS: 'Parameters',
|
||||
engine_api.STACK_ID: 'StackId',
|
||||
|
@ -200,6 +199,9 @@ class StackController(object):
|
|||
engine_api.STACK_TIMEOUT: 'TimeoutInMinutes',
|
||||
}
|
||||
|
||||
if s[engine_api.STACK_UPDATED_TIME] is not None:
|
||||
keymap[engine_api.STACK_UPDATED_TIME] = 'LastUpdatedTime'
|
||||
|
||||
result = api_utils.reformat_dict_keys(keymap, s)
|
||||
|
||||
action = s[engine_api.STACK_ACTION]
|
||||
|
|
|
@ -119,6 +119,11 @@ class Stack(BASE, HeatBase, SoftDelete):
|
|||
stack_user_project_id = sqlalchemy.Column(sqlalchemy.String(64),
|
||||
nullable=True)
|
||||
|
||||
# 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
|
||||
# created/modified. (bug #1193269)
|
||||
updated_at = sqlalchemy.Column(sqlalchemy.DateTime)
|
||||
|
||||
|
||||
class StackLock(BASE, HeatBase):
|
||||
"""Store stack locks for deployments with multiple-engines."""
|
||||
|
|
|
@ -81,11 +81,12 @@ def format_stack(stack):
|
|||
Return a representation of the given stack that matches the API output
|
||||
expectations.
|
||||
'''
|
||||
updated_time = stack.updated_time and timeutils.isotime(stack.updated_time)
|
||||
info = {
|
||||
api.STACK_NAME: stack.name,
|
||||
api.STACK_ID: dict(stack.identifier()),
|
||||
api.STACK_CREATION_TIME: timeutils.isotime(stack.created_time),
|
||||
api.STACK_UPDATED_TIME: timeutils.isotime(stack.updated_time),
|
||||
api.STACK_UPDATED_TIME: updated_time,
|
||||
api.STACK_NOTIFICATION_TOPICS: [], # TODO Not implemented yet
|
||||
api.STACK_PARAMETERS: stack.parameters.map(str),
|
||||
api.STACK_DESCRIPTION: stack.t[stack.t.DESCRIPTION],
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
import collections
|
||||
import copy
|
||||
import functools
|
||||
from datetime import datetime
|
||||
import re
|
||||
import six
|
||||
|
||||
|
@ -29,7 +29,6 @@ from heat.engine import function
|
|||
from heat.engine import resource
|
||||
from heat.engine import resources
|
||||
from heat.engine import scheduler
|
||||
from heat.engine import timestamp
|
||||
from heat.engine import update
|
||||
from heat.engine.notification import stack as notification
|
||||
from heat.engine.parameter_groups import ParameterGroups
|
||||
|
@ -55,20 +54,14 @@ class Stack(collections.Mapping):
|
|||
STATUSES = (IN_PROGRESS, FAILED, COMPLETE
|
||||
) = ('IN_PROGRESS', 'FAILED', 'COMPLETE')
|
||||
|
||||
created_time = timestamp.Timestamp(functools.partial(db_api.stack_get,
|
||||
show_deleted=True),
|
||||
'created_at')
|
||||
updated_time = timestamp.Timestamp(functools.partial(db_api.stack_get,
|
||||
show_deleted=True),
|
||||
'updated_at')
|
||||
|
||||
_zones = None
|
||||
|
||||
def __init__(self, context, stack_name, tmpl, env=None,
|
||||
stack_id=None, action=None, status=None,
|
||||
status_reason='', timeout_mins=60, resolve_data=True,
|
||||
disable_rollback=True, parent_resource=None, owner_id=None,
|
||||
adopt_stack_data=None, stack_user_project_id=None):
|
||||
adopt_stack_data=None, stack_user_project_id=None,
|
||||
created_time=None, updated_time=None):
|
||||
'''
|
||||
Initialise from a context, name, Template object and (optionally)
|
||||
Environment object. The database ID may also be initialised, if the
|
||||
|
@ -99,6 +92,8 @@ class Stack(collections.Mapping):
|
|||
self._access_allowed_handlers = {}
|
||||
self.adopt_stack_data = adopt_stack_data
|
||||
self.stack_user_project_id = stack_user_project_id
|
||||
self.created_time = created_time
|
||||
self.updated_time = updated_time
|
||||
|
||||
resources.initialise()
|
||||
|
||||
|
@ -191,7 +186,9 @@ class Stack(collections.Mapping):
|
|||
stack.id, stack.action, stack.status, stack.status_reason,
|
||||
stack.timeout, resolve_data, stack.disable_rollback,
|
||||
parent_resource, owner_id=stack.owner_id,
|
||||
stack_user_project_id=stack.stack_user_project_id)
|
||||
stack_user_project_id=stack.stack_user_project_id,
|
||||
created_time=stack.created_at,
|
||||
updated_time=stack.updated_at)
|
||||
|
||||
return stack
|
||||
|
||||
|
@ -200,7 +197,6 @@ class Stack(collections.Mapping):
|
|||
Store the stack in the database and return its ID
|
||||
If self.id is set, we update the existing stack
|
||||
'''
|
||||
|
||||
s = {
|
||||
'name': self._backup_name() if backup else self.name,
|
||||
'raw_template_id': self.t.store(self.context),
|
||||
|
@ -214,6 +210,7 @@ class Stack(collections.Mapping):
|
|||
'timeout': self.timeout_mins,
|
||||
'disable_rollback': self.disable_rollback,
|
||||
'stack_user_project_id': self.stack_user_project_id,
|
||||
'updated_at': self.updated_time,
|
||||
}
|
||||
if self.id:
|
||||
db_api.stack_update(self.context, self.id, s)
|
||||
|
@ -229,6 +226,7 @@ class Stack(collections.Mapping):
|
|||
|
||||
new_s = db_api.stack_create(self.context, s)
|
||||
self.id = new_s.id
|
||||
self.created_time = new_s.created_at
|
||||
|
||||
self._set_param_stackid()
|
||||
|
||||
|
@ -518,6 +516,7 @@ class Stack(collections.Mapping):
|
|||
Update will fail if it exceeds the specified timeout. The default is
|
||||
60 minutes, set in the constructor
|
||||
'''
|
||||
self.updated_time = datetime.utcnow()
|
||||
updater = scheduler.TaskRunner(self.update_task, newstack)
|
||||
updater()
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
import copy
|
||||
import base64
|
||||
from datetime import datetime
|
||||
|
||||
from heat.engine import event
|
||||
from heat.common import exception
|
||||
|
@ -751,9 +750,6 @@ class Resource(object):
|
|||
|
||||
new_rs = db_api.resource_create(self.context, rs)
|
||||
self.id = new_rs.id
|
||||
|
||||
self.stack.updated_time = datetime.utcnow()
|
||||
|
||||
except Exception as ex:
|
||||
logger.error(_('DB error %s') % str(ex))
|
||||
|
||||
|
@ -781,8 +777,6 @@ class Resource(object):
|
|||
'status_reason': reason,
|
||||
'stack_id': self.stack.id,
|
||||
'nova_instance': self.resource_id})
|
||||
|
||||
self.stack.updated_time = datetime.utcnow()
|
||||
except Exception as ex:
|
||||
logger.error(_('DB error %s') % str(ex))
|
||||
|
||||
|
|
|
@ -207,6 +207,54 @@ class CfnStackControllerTest(HeatTestCase):
|
|||
'version': self.api_version},
|
||||
None)
|
||||
|
||||
def test_describe_last_updated_time(self):
|
||||
params = {'Action': 'DescribeStacks'}
|
||||
dummy_req = self._dummy_GET_request(params)
|
||||
self._stub_enforce(dummy_req, 'DescribeStacks')
|
||||
|
||||
engine_resp = [{u'updated_time': '1970-01-01',
|
||||
u'parameters': {},
|
||||
u'stack_action': u'CREATE',
|
||||
u'stack_status': u'COMPLETE'}]
|
||||
|
||||
self.m.StubOutWithMock(rpc, 'call')
|
||||
rpc.call(dummy_req.context, self.topic,
|
||||
{'namespace': None,
|
||||
'method': 'show_stack',
|
||||
'args': {'stack_identity': None},
|
||||
'version': self.api_version}, None).AndReturn(engine_resp)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
response = self.controller.describe(dummy_req)
|
||||
result = response['DescribeStacksResponse']['DescribeStacksResult']
|
||||
stack = result['Stacks'][0]
|
||||
self.assertEqual('1970-01-01', stack['LastUpdatedTime'])
|
||||
|
||||
def test_describe_no_last_updated_time(self):
|
||||
params = {'Action': 'DescribeStacks'}
|
||||
dummy_req = self._dummy_GET_request(params)
|
||||
self._stub_enforce(dummy_req, 'DescribeStacks')
|
||||
|
||||
engine_resp = [{u'updated_time': None,
|
||||
u'parameters': {},
|
||||
u'stack_action': u'CREATE',
|
||||
u'stack_status': u'COMPLETE'}]
|
||||
|
||||
self.m.StubOutWithMock(rpc, 'call')
|
||||
rpc.call(dummy_req.context, self.topic,
|
||||
{'namespace': None,
|
||||
'method': 'show_stack',
|
||||
'args': {'stack_identity': None},
|
||||
'version': self.api_version}, None).AndReturn(engine_resp)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
response = self.controller.describe(dummy_req)
|
||||
result = response['DescribeStacksResponse']['DescribeStacksResult']
|
||||
stack = result['Stacks'][0]
|
||||
self.assertNotIn('LastUpdatedTime', stack)
|
||||
|
||||
def test_describe(self):
|
||||
# Format a dummy GET request to pass into the WSGI handler
|
||||
stack_name = u"wordpress"
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
import json
|
||||
import mock
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
import heat.engine.api as api
|
||||
|
||||
|
@ -189,8 +190,6 @@ class FormatTest(HeatTestCase):
|
|||
event_id_formatted['path'])
|
||||
self.assertEqual(event_id, event_identifier.event_id)
|
||||
|
||||
@mock.patch.object(parser.Stack, 'updated_time', new=None)
|
||||
@mock.patch.object(parser.Stack, 'created_time', new=None)
|
||||
@mock.patch.object(api, 'format_stack_resource')
|
||||
def test_format_stack_preview(self, mock_fmt_resource):
|
||||
def mock_format_resources(res):
|
||||
|
@ -206,6 +205,58 @@ class FormatTest(HeatTestCase):
|
|||
self.assertIn('resources', stack)
|
||||
self.assertEqual(['fmt1', ['fmt2', ['fmt3']]], stack['resources'])
|
||||
|
||||
def test_format_stack(self):
|
||||
self.stack.created_time = datetime(1970, 1, 1)
|
||||
info = api.format_stack(self.stack)
|
||||
|
||||
aws_id = ('arn:openstack:heat::test_tenant_id:'
|
||||
'stacks/test_stack/' + self.stack.id)
|
||||
expected_stack_info = {
|
||||
'capabilities': [],
|
||||
'creation_time': '1970-01-01T00:00:00Z',
|
||||
'description': 'No description',
|
||||
'disable_rollback': True,
|
||||
'notification_topics': [],
|
||||
'stack_action': '',
|
||||
'stack_name': 'test_stack',
|
||||
'stack_status': '',
|
||||
'stack_status_reason': '',
|
||||
'template_description': 'No description',
|
||||
'timeout_mins': 60,
|
||||
'parameters': {
|
||||
'AWS::Region': 'ap-southeast-1',
|
||||
'AWS::StackId': aws_id,
|
||||
'AWS::StackName': 'test_stack'},
|
||||
'stack_identity': {
|
||||
'path': '',
|
||||
'stack_id': self.stack.id,
|
||||
'stack_name': 'test_stack',
|
||||
'tenant': 'test_tenant_id'},
|
||||
'updated_time': None}
|
||||
self.assertEqual(expected_stack_info, info)
|
||||
|
||||
def test_format_stack_created_time(self):
|
||||
self.stack.created_time = None
|
||||
info = api.format_stack(self.stack)
|
||||
self.assertIsNotNone(info['creation_time'])
|
||||
|
||||
def test_format_stack_updated_time(self):
|
||||
self.stack.updated_time = None
|
||||
info = api.format_stack(self.stack)
|
||||
self.assertIsNone(info['updated_time'])
|
||||
|
||||
self.stack.updated_time = datetime(1970, 1, 1)
|
||||
info = api.format_stack(self.stack)
|
||||
self.assertEqual('1970-01-01T00:00:00Z', info['updated_time'])
|
||||
|
||||
@mock.patch.object(api, 'format_stack_outputs')
|
||||
def test_format_stack_adds_outputs(self, mock_fmt_outputs):
|
||||
mock_fmt_outputs.return_value = 'foobar'
|
||||
self.stack.action = 'CREATE'
|
||||
self.stack.status = 'COMPLETE'
|
||||
info = api.format_stack(self.stack)
|
||||
self.assertEqual('foobar', info[rpc_api.STACK_OUTPUTS])
|
||||
|
||||
|
||||
class FormatValidateParameterTest(HeatTestCase):
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import mock
|
|||
import time
|
||||
|
||||
from keystoneclient import exceptions as kc_exceptions
|
||||
|
||||
from mox import IgnoreArg
|
||||
from oslo.config import cfg
|
||||
|
||||
from heat.engine import environment
|
||||
|
@ -885,7 +885,9 @@ class StackTest(HeatTestCase):
|
|||
stack.action, stack.status, stack.status_reason,
|
||||
stack.timeout, True, stack.disable_rollback,
|
||||
'parent', owner_id=None,
|
||||
stack_user_project_id=None)
|
||||
stack_user_project_id=None,
|
||||
created_time=IgnoreArg(),
|
||||
updated_time=None)
|
||||
|
||||
self.m.ReplayAll()
|
||||
parser.Stack.load(self.ctx, stack_id=self.stack.id,
|
||||
|
@ -1012,14 +1014,17 @@ class StackTest(HeatTestCase):
|
|||
|
||||
@utils.stack_delete_after
|
||||
def test_updated_time(self):
|
||||
self.stack = parser.Stack(self.ctx, 'update_time_test',
|
||||
self.stack = parser.Stack(self.ctx, 'updated_time_test',
|
||||
parser.Template({}))
|
||||
self.assertIsNone(self.stack.updated_time)
|
||||
self.stack.store()
|
||||
stored_time = self.stack.updated_time
|
||||
self.stack.state_set(self.stack.CREATE, self.stack.IN_PROGRESS, 'test')
|
||||
self.stack.create()
|
||||
|
||||
tmpl = {'Resources': {'R1': {'Type': 'GenericResourceType'}}}
|
||||
newstack = parser.Stack(self.ctx, 'updated_time_test',
|
||||
parser.Template(tmpl))
|
||||
self.stack.update(newstack)
|
||||
self.assertIsNotNone(self.stack.updated_time)
|
||||
self.assertNotEqual(self.stack.updated_time, stored_time)
|
||||
|
||||
@utils.stack_delete_after
|
||||
def test_delete(self):
|
||||
|
@ -1367,7 +1372,7 @@ class StackTest(HeatTestCase):
|
|||
self.stack.store()
|
||||
self.assertEqual((parser.Stack.CREATE, parser.Stack.FAILED),
|
||||
self.stack.state)
|
||||
self.stack.update({})
|
||||
self.stack.update(mock.Mock())
|
||||
self.assertEqual((parser.Stack.UPDATE, parser.Stack.FAILED),
|
||||
self.stack.state)
|
||||
|
||||
|
|
Loading…
Reference in New Issue