~ubuntu-branches/ubuntu/trusty/heat/trusty

« back to all changes in this revision

Viewing changes to .pc/rename-quantumclient.patch/heat/engine/resource.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Chuck Short
  • Date: 2013-08-08 01:08:42 UTC
  • Revision ID: package-import@ubuntu.com-20130808010842-77cni2v4vlib7rus
Tags: 2013.2~b2-0ubuntu4
[ Chuck Short ]
* debian/rules: Enable testsuite during builds.
* debian/patches/fix-sqlalchemy-0.8.patch: Build against sqlalchemy 0.8.
* debian/patches/rename-quantumclient.patch: quantumclient -> neutronclient.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
#
 
4
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 
5
#    not use this file except in compliance with the License. You may obtain
 
6
#    a copy of the License at
 
7
#
 
8
#         http://www.apache.org/licenses/LICENSE-2.0
 
9
#
 
10
#    Unless required by applicable law or agreed to in writing, software
 
11
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
12
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
13
#    License for the specific language governing permissions and limitations
 
14
#    under the License.
 
15
 
 
16
import base64
 
17
from datetime import datetime
 
18
 
 
19
from heat.engine import event
 
20
from heat.common import exception
 
21
from heat.openstack.common import excutils
 
22
from heat.db import api as db_api
 
23
from heat.common import identifier
 
24
from heat.common import short_id
 
25
from heat.engine import timestamp
 
26
# import class to avoid name collisions and ugly aliasing
 
27
from heat.engine.attributes import Attributes
 
28
from heat.engine.properties import Properties
 
29
 
 
30
from heat.openstack.common import log as logging
 
31
from heat.openstack.common.gettextutils import _
 
32
 
 
33
logger = logging.getLogger(__name__)
 
34
 
 
35
 
 
36
_resource_classes = {}
 
37
_template_class = None
 
38
 
 
39
 
 
40
def get_types():
 
41
    '''Return an iterator over the list of valid resource types.'''
 
42
    return iter(_resource_classes)
 
43
 
 
44
 
 
45
def get_class(resource_type, resource_name=None, environment=None):
 
46
    '''Return the Resource class for a given resource type.'''
 
47
    if environment:
 
48
        resource_type = environment.get_resource_type(resource_type,
 
49
                                                      resource_name)
 
50
 
 
51
    if resource_type.endswith(('.yaml', '.template')):
 
52
        cls = _template_class
 
53
    else:
 
54
        cls = _resource_classes.get(resource_type)
 
55
    if cls is None:
 
56
        msg = "Unknown resource Type : %s" % resource_type
 
57
        raise exception.StackValidationFailed(message=msg)
 
58
    else:
 
59
        return cls
 
60
 
 
61
 
 
62
def _register_class(resource_type, resource_class):
 
63
    logger.info(_('Registering resource type %s') % resource_type)
 
64
    if resource_type in _resource_classes:
 
65
        logger.warning(_('Replacing existing resource type %s') %
 
66
                       resource_type)
 
67
 
 
68
    _resource_classes[resource_type] = resource_class
 
69
 
 
70
 
 
71
def register_template_class(cls):
 
72
    global _template_class
 
73
    if _template_class is None:
 
74
        _template_class = cls
 
75
 
 
76
 
 
77
class UpdateReplace(Exception):
 
78
    '''
 
79
    Raised when resource update requires replacement
 
80
    '''
 
81
    _message = _("The Resource %s requires replacement.")
 
82
 
 
83
    def __init__(self, resource_name='Unknown',
 
84
                 message=_("The Resource %s requires replacement.")):
 
85
        try:
 
86
            msg = message % resource_name
 
87
        except TypeError:
 
88
            msg = message
 
89
        super(Exception, self).__init__(msg)
 
90
 
 
91
 
 
92
class Metadata(object):
 
93
    '''
 
94
    A descriptor for accessing the metadata of a resource while ensuring the
 
95
    most up-to-date data is always obtained from the database.
 
96
    '''
 
97
 
 
98
    def __get__(self, resource, resource_class):
 
99
        '''Return the metadata for the owning resource.'''
 
100
        if resource is None:
 
101
            return None
 
102
        if resource.id is None:
 
103
            return resource.parsed_template('Metadata')
 
104
        rs = db_api.resource_get(resource.stack.context, resource.id)
 
105
        rs.refresh(attrs=['rsrc_metadata'])
 
106
        return rs.rsrc_metadata
 
107
 
 
108
    def __set__(self, resource, metadata):
 
109
        '''Update the metadata for the owning resource.'''
 
110
        if resource.id is None:
 
111
            raise exception.ResourceNotAvailable(resource_name=resource.name)
 
112
        rs = db_api.resource_get(resource.stack.context, resource.id)
 
113
        rs.update_and_save({'rsrc_metadata': metadata})
 
114
 
 
115
 
 
116
class Resource(object):
 
117
    ACTIONS = (CREATE, DELETE, UPDATE, ROLLBACK, SUSPEND, RESUME
 
118
               ) = ('CREATE', 'DELETE', 'UPDATE', 'ROLLBACK',
 
119
                    'SUSPEND', 'RESUME')
 
120
 
 
121
    STATUSES = (IN_PROGRESS, FAILED, COMPLETE
 
122
                ) = ('IN_PROGRESS', 'FAILED', 'COMPLETE')
 
123
 
 
124
    # If True, this resource must be created before it can be referenced.
 
125
    strict_dependency = True
 
126
 
 
127
    created_time = timestamp.Timestamp(db_api.resource_get, 'created_at')
 
128
    updated_time = timestamp.Timestamp(db_api.resource_get, 'updated_at')
 
129
 
 
130
    metadata = Metadata()
 
131
 
 
132
    # Resource implementation set this to the subset of template keys
 
133
    # which are supported for handle_update, used by update_template_diff
 
134
    update_allowed_keys = ()
 
135
 
 
136
    # Resource implementation set this to the subset of resource properties
 
137
    # supported for handle_update, used by update_template_diff_properties
 
138
    update_allowed_properties = ()
 
139
 
 
140
    # Resource implementations set this to the name: description dictionary
 
141
    # that describes the appropriate resource attributes
 
142
    attributes_schema = {}
 
143
 
 
144
    def __new__(cls, name, json, stack):
 
145
        '''Create a new Resource of the appropriate class for its type.'''
 
146
 
 
147
        if cls != Resource:
 
148
            # Call is already for a subclass, so pass it through
 
149
            return super(Resource, cls).__new__(cls)
 
150
 
 
151
        # Select the correct subclass to instantiate
 
152
        ResourceClass = get_class(json['Type'],
 
153
                                  resource_name=name,
 
154
                                  environment=stack.env)
 
155
        return ResourceClass(name, json, stack)
 
156
 
 
157
    def __init__(self, name, json_snippet, stack):
 
158
        if '/' in name:
 
159
            raise ValueError(_('Resource name may not contain "/"'))
 
160
 
 
161
        self.stack = stack
 
162
        self.context = stack.context
 
163
        self.name = name
 
164
        self.json_snippet = json_snippet
 
165
        self.t = stack.resolve_static_data(json_snippet)
 
166
        self.properties = Properties(self.properties_schema,
 
167
                                     self.t.get('Properties', {}),
 
168
                                     self.stack.resolve_runtime_data,
 
169
                                     self.name)
 
170
        self.attributes = Attributes(self.name,
 
171
                                     self.attributes_schema,
 
172
                                     self._resolve_attribute)
 
173
 
 
174
        resource = db_api.resource_get_by_name_and_stack(self.context,
 
175
                                                         name, stack.id)
 
176
        if resource:
 
177
            self.resource_id = resource.nova_instance
 
178
            self.action = resource.action
 
179
            self.status = resource.status
 
180
            self.status_reason = resource.status_reason
 
181
            self.id = resource.id
 
182
            self.data = resource.data
 
183
        else:
 
184
            self.resource_id = None
 
185
            self.action = None
 
186
            self.status = None
 
187
            self.status_reason = ''
 
188
            self.id = None
 
189
            self.data = []
 
190
 
 
191
    def __eq__(self, other):
 
192
        '''Allow == comparison of two resources.'''
 
193
        # For the purposes of comparison, we declare two resource objects
 
194
        # equal if their names and parsed_templates are the same
 
195
        if isinstance(other, Resource):
 
196
            return (self.name == other.name) and (
 
197
                self.parsed_template() == other.parsed_template())
 
198
        return NotImplemented
 
199
 
 
200
    def __ne__(self, other):
 
201
        '''Allow != comparison of two resources.'''
 
202
        result = self.__eq__(other)
 
203
        if result is NotImplemented:
 
204
            return result
 
205
        return not result
 
206
 
 
207
    def type(self):
 
208
        return self.t['Type']
 
209
 
 
210
    def identifier(self):
 
211
        '''Return an identifier for this resource.'''
 
212
        return identifier.ResourceIdentifier(resource_name=self.name,
 
213
                                             **self.stack.identifier())
 
214
 
 
215
    def parsed_template(self, section=None, default={}):
 
216
        '''
 
217
        Return the parsed template data for the resource. May be limited to
 
218
        only one section of the data, in which case a default value may also
 
219
        be supplied.
 
220
        '''
 
221
        if section is None:
 
222
            template = self.t
 
223
        else:
 
224
            template = self.t.get(section, default)
 
225
        return self.stack.resolve_runtime_data(template)
 
226
 
 
227
    def update_template_diff(self, after, before):
 
228
        '''
 
229
        Returns the difference between the before and after json snippets. If
 
230
        something has been removed in after which exists in before we set it to
 
231
        None. If any keys have changed which are not in update_allowed_keys,
 
232
        raises UpdateReplace if the differing keys are not in
 
233
        update_allowed_keys
 
234
        '''
 
235
        update_allowed_set = set(self.update_allowed_keys)
 
236
 
 
237
        # Create a set containing the keys in both current and update template
 
238
        template_keys = set(before.keys())
 
239
        template_keys.update(set(after.keys()))
 
240
 
 
241
        # Create a set of keys which differ (or are missing/added)
 
242
        changed_keys_set = set([k for k in template_keys
 
243
                                if before.get(k) != after.get(k)])
 
244
 
 
245
        if not changed_keys_set.issubset(update_allowed_set):
 
246
            badkeys = changed_keys_set - update_allowed_set
 
247
            raise UpdateReplace(self.name)
 
248
 
 
249
        return dict((k, after.get(k)) for k in changed_keys_set)
 
250
 
 
251
    def update_template_diff_properties(self, after, before):
 
252
        '''
 
253
        Returns the changed Properties between the before and after json
 
254
        snippets. If a property has been removed in after which exists in
 
255
        before we set it to None. If any properties have changed which are not
 
256
        in update_allowed_properties, raises UpdateReplace if the modified
 
257
        properties are not in the update_allowed_properties
 
258
        '''
 
259
        update_allowed_set = set(self.update_allowed_properties)
 
260
 
 
261
        # Create a set containing the keys in both current and update template
 
262
        current_properties = before.get('Properties', {})
 
263
 
 
264
        template_properties = set(current_properties.keys())
 
265
        updated_properties = after.get('Properties', {})
 
266
        template_properties.update(set(updated_properties.keys()))
 
267
 
 
268
        # Create a set of keys which differ (or are missing/added)
 
269
        changed_properties_set = set(k for k in template_properties
 
270
                                     if current_properties.get(k) !=
 
271
                                     updated_properties.get(k))
 
272
 
 
273
        if not changed_properties_set.issubset(update_allowed_set):
 
274
            raise UpdateReplace(self.name)
 
275
 
 
276
        return dict((k, updated_properties.get(k))
 
277
                    for k in changed_properties_set)
 
278
 
 
279
    def __str__(self):
 
280
        return '%s "%s"' % (self.__class__.__name__, self.name)
 
281
 
 
282
    def _add_dependencies(self, deps, head, fragment):
 
283
        if isinstance(fragment, dict):
 
284
            for key, value in fragment.items():
 
285
                if key in ('DependsOn', 'Ref', 'Fn::GetAtt'):
 
286
                    if key == 'Fn::GetAtt':
 
287
                        value, head = value
 
288
 
 
289
                    try:
 
290
                        target = self.stack.resources[value]
 
291
                    except KeyError:
 
292
                        raise exception.InvalidTemplateReference(
 
293
                            resource=value,
 
294
                            key=head)
 
295
                    if key == 'DependsOn' or target.strict_dependency:
 
296
                        deps += (self, target)
 
297
                else:
 
298
                    self._add_dependencies(deps, key, value)
 
299
        elif isinstance(fragment, list):
 
300
            for item in fragment:
 
301
                self._add_dependencies(deps, head, item)
 
302
 
 
303
    def add_dependencies(self, deps):
 
304
        self._add_dependencies(deps, None, self.t)
 
305
        deps += (self, None)
 
306
 
 
307
    def required_by(self):
 
308
        '''
 
309
        Returns a list of names of resources which directly require this
 
310
        resource as a dependency.
 
311
        '''
 
312
        return list(
 
313
            [r.name for r in self.stack.dependencies.required_by(self)])
 
314
 
 
315
    def keystone(self):
 
316
        return self.stack.clients.keystone()
 
317
 
 
318
    def nova(self, service_type='compute'):
 
319
        return self.stack.clients.nova(service_type)
 
320
 
 
321
    def swift(self):
 
322
        return self.stack.clients.swift()
 
323
 
 
324
    def quantum(self):
 
325
        return self.stack.clients.quantum()
 
326
 
 
327
    def cinder(self):
 
328
        return self.stack.clients.cinder()
 
329
 
 
330
    def _do_action(self, action, pre_func=None):
 
331
        '''
 
332
        Perform a transition to a new state via a specified action
 
333
        action should be e.g self.CREATE, self.UPDATE etc, we set
 
334
        status based on this, the transistion is handled by calling the
 
335
        corresponding handle_* and check_*_complete functions
 
336
        Note pre_func is an optional function reference which will
 
337
        be called before the handle_<action> function
 
338
 
 
339
        If the resource does not declare a check_$action_complete function,
 
340
        we declare COMPLETE status as soon as the handle_$action call has
 
341
        finished, and if no handle_$action function is declared, then we do
 
342
        nothing, useful e.g if the resource requires no action for a given
 
343
        state transition
 
344
        '''
 
345
        assert action in self.ACTIONS, 'Invalid action %s' % action
 
346
 
 
347
        try:
 
348
            self.state_set(action, self.IN_PROGRESS)
 
349
 
 
350
            action_l = action.lower()
 
351
            handle = getattr(self, 'handle_%s' % action_l, None)
 
352
            check = getattr(self, 'check_%s_complete' % action_l, None)
 
353
 
 
354
            if callable(pre_func):
 
355
                pre_func()
 
356
 
 
357
            handle_data = None
 
358
            if callable(handle):
 
359
                handle_data = handle()
 
360
                yield
 
361
                if callable(check):
 
362
                    while not check(handle_data):
 
363
                        yield
 
364
        except Exception as ex:
 
365
            logger.exception('%s : %s' % (action, str(self)))
 
366
            failure = exception.ResourceFailure(ex)
 
367
            self.state_set(action, self.FAILED, str(failure))
 
368
            raise failure
 
369
        except:
 
370
            with excutils.save_and_reraise_exception():
 
371
                try:
 
372
                    self.state_set(action, self.FAILED,
 
373
                                   '%s aborted' % action)
 
374
                except Exception:
 
375
                    logger.exception('Error marking resource as failed')
 
376
        else:
 
377
            self.state_set(action, self.COMPLETE)
 
378
 
 
379
    def create(self):
 
380
        '''
 
381
        Create the resource. Subclasses should provide a handle_create() method
 
382
        to customise creation.
 
383
        '''
 
384
        assert None in (self.action, self.status), 'invalid state for create'
 
385
 
 
386
        logger.info('creating %s' % str(self))
 
387
 
 
388
        # Re-resolve the template, since if the resource Ref's
 
389
        # the AWS::StackId pseudo parameter, it will change after
 
390
        # the parser.Stack is stored (which is after the resources
 
391
        # are __init__'d, but before they are create()'d)
 
392
        self.t = self.stack.resolve_static_data(self.json_snippet)
 
393
        self.properties = Properties(self.properties_schema,
 
394
                                     self.t.get('Properties', {}),
 
395
                                     self.stack.resolve_runtime_data,
 
396
                                     self.name)
 
397
        return self._do_action(self.CREATE, self.properties.validate)
 
398
 
 
399
    def update(self, after, before=None):
 
400
        '''
 
401
        update the resource. Subclasses should provide a handle_update() method
 
402
        to customise update, the base-class handle_update will fail by default.
 
403
        '''
 
404
        if before is None:
 
405
            before = self.parsed_template()
 
406
 
 
407
        if (self.action, self.status) in ((self.CREATE, self.IN_PROGRESS),
 
408
                                         (self.UPDATE, self.IN_PROGRESS)):
 
409
            raise exception.ResourceFailure(Exception(
 
410
                'Resource update already requested'))
 
411
 
 
412
        logger.info('updating %s' % str(self))
 
413
 
 
414
        try:
 
415
            self.state_set(self.UPDATE, self.IN_PROGRESS)
 
416
            properties = Properties(self.properties_schema,
 
417
                                    after.get('Properties', {}),
 
418
                                    self.stack.resolve_runtime_data,
 
419
                                    self.name)
 
420
            properties.validate()
 
421
            tmpl_diff = self.update_template_diff(after, before)
 
422
            prop_diff = self.update_template_diff_properties(after, before)
 
423
            if callable(getattr(self, 'handle_update', None)):
 
424
                result = self.handle_update(after, tmpl_diff, prop_diff)
 
425
        except UpdateReplace:
 
426
            logger.debug("Resource %s update requires replacement" % self.name)
 
427
            raise
 
428
        except Exception as ex:
 
429
            logger.exception('update %s : %s' % (str(self), str(ex)))
 
430
            failure = exception.ResourceFailure(ex)
 
431
            self.state_set(self.UPDATE, self.FAILED, str(failure))
 
432
            raise failure
 
433
        else:
 
434
            self.t = self.stack.resolve_static_data(after)
 
435
            self.state_set(self.UPDATE, self.COMPLETE)
 
436
 
 
437
    def suspend(self):
 
438
        '''
 
439
        Suspend the resource.  Subclasses should provide a handle_suspend()
 
440
        method to implement suspend
 
441
        '''
 
442
        # Don't try to suspend the resource unless it's in a stable state
 
443
        if (self.action == self.DELETE or self.status != self.COMPLETE):
 
444
            exc = exception.Error('State %s invalid for suspend'
 
445
                                  % str(self.state))
 
446
            raise exception.ResourceFailure(exc)
 
447
 
 
448
        logger.info('suspending %s' % str(self))
 
449
        return self._do_action(self.SUSPEND)
 
450
 
 
451
    def resume(self):
 
452
        '''
 
453
        Resume the resource.  Subclasses should provide a handle_resume()
 
454
        method to implement resume
 
455
        '''
 
456
        # Can't resume a resource unless it's SUSPEND_COMPLETE
 
457
        if self.state != (self.SUSPEND, self.COMPLETE):
 
458
            exc = exception.Error('State %s invalid for resume'
 
459
                                  % str(self.state))
 
460
            raise exception.ResourceFailure(exc)
 
461
 
 
462
        logger.info('resuming %s' % str(self))
 
463
        return self._do_action(self.RESUME)
 
464
 
 
465
    def physical_resource_name(self):
 
466
        if self.id is None:
 
467
            return None
 
468
 
 
469
        return '%s-%s-%s' % (self.stack.name,
 
470
                             self.name,
 
471
                             short_id.get_id(self.id))
 
472
 
 
473
    def validate(self):
 
474
        logger.info('Validating %s' % str(self))
 
475
 
 
476
        self.validate_deletion_policy(self.t)
 
477
        return self.properties.validate()
 
478
 
 
479
    @classmethod
 
480
    def validate_deletion_policy(cls, template):
 
481
        deletion_policy = template.get('DeletionPolicy', 'Delete')
 
482
        if deletion_policy not in ('Delete', 'Retain', 'Snapshot'):
 
483
            msg = 'Invalid DeletionPolicy %s' % deletion_policy
 
484
            raise exception.StackValidationFailed(message=msg)
 
485
        elif deletion_policy == 'Snapshot':
 
486
            if not callable(getattr(cls, 'handle_snapshot_delete', None)):
 
487
                msg = 'Snapshot DeletionPolicy not supported'
 
488
                raise exception.StackValidationFailed(message=msg)
 
489
 
 
490
    def delete(self):
 
491
        '''
 
492
        Delete the resource. Subclasses should provide a handle_delete() method
 
493
        to customise deletion.
 
494
        '''
 
495
        if (self.action, self.status) == (self.DELETE, self.COMPLETE):
 
496
            return
 
497
        # No need to delete if the resource has never been created
 
498
        if self.action is None:
 
499
            return
 
500
 
 
501
        initial_state = self.state
 
502
 
 
503
        logger.info('deleting %s' % str(self))
 
504
 
 
505
        try:
 
506
            self.state_set(self.DELETE, self.IN_PROGRESS)
 
507
 
 
508
            deletion_policy = self.t.get('DeletionPolicy', 'Delete')
 
509
            if deletion_policy == 'Delete':
 
510
                if callable(getattr(self, 'handle_delete', None)):
 
511
                    self.handle_delete()
 
512
            elif deletion_policy == 'Snapshot':
 
513
                if callable(getattr(self, 'handle_snapshot_delete', None)):
 
514
                    self.handle_snapshot_delete(initial_state)
 
515
        except Exception as ex:
 
516
            logger.exception('Delete %s', str(self))
 
517
            failure = exception.ResourceFailure(ex)
 
518
            self.state_set(self.DELETE, self.FAILED, str(failure))
 
519
            raise failure
 
520
        except:
 
521
            with excutils.save_and_reraise_exception():
 
522
                try:
 
523
                    self.state_set(self.DELETE, self.FAILED,
 
524
                                   'Deletion aborted')
 
525
                except Exception:
 
526
                    logger.exception('Error marking resource deletion failed')
 
527
        else:
 
528
            self.state_set(self.DELETE, self.COMPLETE)
 
529
 
 
530
    def destroy(self):
 
531
        '''
 
532
        Delete the resource and remove it from the database.
 
533
        '''
 
534
        self.delete()
 
535
 
 
536
        if self.id is None:
 
537
            return
 
538
 
 
539
        try:
 
540
            db_api.resource_get(self.context, self.id).delete()
 
541
        except exception.NotFound:
 
542
            # Don't fail on delete if the db entry has
 
543
            # not been created yet.
 
544
            pass
 
545
 
 
546
        self.id = None
 
547
 
 
548
    def resource_id_set(self, inst):
 
549
        self.resource_id = inst
 
550
        if self.id is not None:
 
551
            try:
 
552
                rs = db_api.resource_get(self.context, self.id)
 
553
                rs.update_and_save({'nova_instance': self.resource_id})
 
554
            except Exception as ex:
 
555
                logger.warn('db error %s' % str(ex))
 
556
 
 
557
    def _store(self):
 
558
        '''Create the resource in the database.'''
 
559
        try:
 
560
            rs = {'action': self.action,
 
561
                  'status': self.status,
 
562
                  'status_reason': self.status_reason,
 
563
                  'stack_id': self.stack.id,
 
564
                  'nova_instance': self.resource_id,
 
565
                  'name': self.name,
 
566
                  'rsrc_metadata': self.metadata,
 
567
                  'stack_name': self.stack.name}
 
568
 
 
569
            new_rs = db_api.resource_create(self.context, rs)
 
570
            self.id = new_rs.id
 
571
 
 
572
            self.stack.updated_time = datetime.utcnow()
 
573
 
 
574
        except Exception as ex:
 
575
            logger.error('DB error %s' % str(ex))
 
576
 
 
577
    def _add_event(self, action, status, reason):
 
578
        '''Add a state change event to the database.'''
 
579
        ev = event.Event(self.context, self.stack, self,
 
580
                         action, status, reason,
 
581
                         self.resource_id, self.properties)
 
582
 
 
583
        try:
 
584
            ev.store()
 
585
        except Exception as ex:
 
586
            logger.error('DB error %s' % str(ex))
 
587
 
 
588
    def _store_or_update(self, action, status, reason):
 
589
        self.action = action
 
590
        self.status = status
 
591
        self.status_reason = reason
 
592
 
 
593
        if self.id is not None:
 
594
            try:
 
595
                rs = db_api.resource_get(self.context, self.id)
 
596
                rs.update_and_save({'action': self.action,
 
597
                                    'status': self.status,
 
598
                                    'status_reason': reason,
 
599
                                    'nova_instance': self.resource_id})
 
600
 
 
601
                self.stack.updated_time = datetime.utcnow()
 
602
            except Exception as ex:
 
603
                logger.error('DB error %s' % str(ex))
 
604
 
 
605
        # store resource in DB on transition to CREATE_IN_PROGRESS
 
606
        # all other transistions (other than to DELETE_COMPLETE)
 
607
        # should be handled by the update_and_save above..
 
608
        elif (action, status) == (self.CREATE, self.IN_PROGRESS):
 
609
            self._store()
 
610
 
 
611
    def _resolve_attribute(self, name):
 
612
        """
 
613
        Default implementation; should be overridden by resources that expose
 
614
        attributes
 
615
 
 
616
        :param name: The attribute to resolve
 
617
        :returns: the resource attribute named key
 
618
        """
 
619
        # By default, no attributes resolve
 
620
        pass
 
621
 
 
622
    def state_set(self, action, status, reason="state changed"):
 
623
        if action not in self.ACTIONS:
 
624
            raise ValueError("Invalid action %s" % action)
 
625
 
 
626
        if status not in self.STATUSES:
 
627
            raise ValueError("Invalid status %s" % status)
 
628
 
 
629
        old_state = (self.action, self.status)
 
630
        new_state = (action, status)
 
631
        self._store_or_update(action, status, reason)
 
632
 
 
633
        if new_state != old_state:
 
634
            self._add_event(action, status, reason)
 
635
 
 
636
    @property
 
637
    def state(self):
 
638
        '''Returns state, tuple of action, status.'''
 
639
        return (self.action, self.status)
 
640
 
 
641
    def FnGetRefId(self):
 
642
        '''
 
643
        http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/\
 
644
        intrinsic-function-reference-ref.html
 
645
        '''
 
646
        if self.resource_id is not None:
 
647
            return unicode(self.resource_id)
 
648
        else:
 
649
            return unicode(self.name)
 
650
 
 
651
    def FnGetAtt(self, key):
 
652
        '''
 
653
        http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/\
 
654
        intrinsic-function-reference-getatt.html
 
655
        '''
 
656
        try:
 
657
            return self.attributes[key]
 
658
        except KeyError:
 
659
            raise exception.InvalidTemplateAttribute(resource=self.name,
 
660
                                                     key=key)
 
661
 
 
662
    def FnBase64(self, data):
 
663
        '''
 
664
        http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/\
 
665
            intrinsic-function-reference-base64.html
 
666
        '''
 
667
        return base64.b64encode(data)
 
668
 
 
669
    def handle_update(self, json_snippet=None, tmpl_diff=None, prop_diff=None):
 
670
        raise UpdateReplace(self.name)
 
671
 
 
672
    def metadata_update(self, new_metadata=None):
 
673
        '''
 
674
        No-op for resources which don't explicitly override this method
 
675
        '''
 
676
        if new_metadata:
 
677
            logger.warning("Resource %s does not implement metadata update" %
 
678
                           self.name)