~ubuntu-branches/ubuntu/saucy/heat/saucy

« back to all changes in this revision

Viewing changes to heat/engine/parser.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Chuck Short, Adam Gandelman
  • Date: 2013-09-08 21:51:19 UTC
  • mfrom: (1.1.4)
  • Revision ID: package-import@ubuntu.com-20130908215119-r939tu4aumqgdrkx
Tags: 2013.2~b3-0ubuntu1
[ Chuck Short ]
* New upstream release.
* debian/control: Add python-netaddr as build-dep.
* debian/heat-common.install: Remove heat-boto and associated man-page
* debian/heat-common.install: Remove heat-cfn and associated man-page
* debian/heat-common.install: Remove heat-watch and associated man-page
* debian/patches/fix-sqlalchemy-0.8.patch: Dropped

[ Adam Gandelman ]
* debian/patches/default-kombu.patch: Dropped.
* debian/patches/default-sqlite.patch: Refreshed.
* debian/*.install, rules: Install heat.conf.sample as common
  config file in heat-common. Drop other per-package configs, they
  are no longer used.
* debian/rules: Clean pbr .egg from build dir if it exists.

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
from heat.openstack.common import log as logging
35
35
from heat.openstack.common.gettextutils import _
36
36
 
37
 
from heat.common.exception import ServerError
38
37
from heat.common.exception import StackValidationFailed
39
38
 
40
39
logger = logging.getLogger(__name__)
51
50
    STATUSES = (IN_PROGRESS, FAILED, COMPLETE
52
51
                ) = ('IN_PROGRESS', 'FAILED', 'COMPLETE')
53
52
 
54
 
    created_time = timestamp.Timestamp(db_api.stack_get, 'created_at')
55
 
    updated_time = timestamp.Timestamp(db_api.stack_get, 'updated_at')
 
53
    created_time = timestamp.Timestamp(functools.partial(db_api.stack_get,
 
54
                                                         show_deleted=True),
 
55
                                       'created_at')
 
56
    updated_time = timestamp.Timestamp(functools.partial(db_api.stack_get,
 
57
                                                         show_deleted=True),
 
58
                                       'updated_at')
56
59
 
57
60
    _zones = None
58
61
 
59
62
    def __init__(self, context, stack_name, tmpl, env=None,
60
63
                 stack_id=None, action=None, status=None,
61
64
                 status_reason='', timeout_mins=60, resolve_data=True,
62
 
                 disable_rollback=True, parent_resource=None):
 
65
                 disable_rollback=True, parent_resource=None, owner_id=None):
63
66
        '''
64
67
        Initialise from a context, name, Template object and (optionally)
65
68
        Environment object. The database ID may also be initialised, if the
66
69
        stack is already in the database.
67
70
        '''
68
71
 
69
 
        if re.match("[a-zA-Z][a-zA-Z0-9_.-]*$", stack_name) is None:
70
 
            raise ValueError(_('Invalid stack name %s'
71
 
                               ' must contain only alphanumeric or '
72
 
                               '\"_-.\" characters, must start with alpha'
73
 
                               ) % stack_name)
 
72
        if owner_id is None:
 
73
            if re.match("[a-zA-Z][a-zA-Z0-9_.-]*$", stack_name) is None:
 
74
                raise ValueError(_('Invalid stack name %s'
 
75
                                   ' must contain only alphanumeric or '
 
76
                                   '\"_-.\" characters, must start with alpha'
 
77
                                   ) % stack_name)
74
78
 
75
79
        self.id = stack_id
 
80
        self.owner_id = owner_id
76
81
        self.context = context
77
82
        self.clients = Clients(context)
78
83
        self.t = tmpl
129
134
 
130
135
    @classmethod
131
136
    def load(cls, context, stack_id=None, stack=None, resolve_data=True,
132
 
             parent_resource=None):
 
137
             parent_resource=None, show_deleted=True):
133
138
        '''Retrieve a Stack from the database.'''
134
139
        if stack is None:
135
 
            stack = db_api.stack_get(context, stack_id)
 
140
            stack = db_api.stack_get(context, stack_id,
 
141
                                     show_deleted=show_deleted)
136
142
        if stack is None:
137
143
            message = 'No stack exists with id "%s"' % str(stack_id)
138
144
            raise exception.NotFound(message)
142
148
        stack = cls(context, stack.name, template, env,
143
149
                    stack.id, stack.action, stack.status, stack.status_reason,
144
150
                    stack.timeout, resolve_data, stack.disable_rollback,
145
 
                    parent_resource)
 
151
                    parent_resource, owner_id=stack.owner_id)
146
152
 
147
153
        return stack
148
154
 
149
 
    def store(self, owner=None):
 
155
    def store(self, backup=False):
150
156
        '''
151
157
        Store the stack in the database and return its ID
152
158
        If self.id is set, we update the existing stack
153
159
        '''
154
 
        new_creds = db_api.user_creds_create(self.context)
155
160
 
156
161
        s = {
157
 
            'name': self.name,
 
162
            'name': self._backup_name() if backup else self.name,
158
163
            'raw_template_id': self.t.store(self.context),
159
164
            'parameters': self.env.user_env_as_dict(),
160
 
            'owner_id': owner and owner.id,
161
 
            'user_creds_id': new_creds.id,
 
165
            'owner_id': self.owner_id,
162
166
            'username': self.context.username,
163
167
            'tenant': self.context.tenant_id,
164
168
            'action': self.action,
170
174
        if self.id:
171
175
            db_api.stack_update(self.context, self.id, s)
172
176
        else:
 
177
            new_creds = db_api.user_creds_create(self.context)
 
178
            s['user_creds_id'] = new_creds.id
173
179
            new_s = db_api.stack_create(self.context, s)
174
180
            self.id = new_s.id
175
181
 
177
183
 
178
184
        return self.id
179
185
 
 
186
    def _backup_name(self):
 
187
        return '%s*' % self.name
 
188
 
180
189
    def identifier(self):
181
190
        '''
182
191
        Return an identifier for this stack.
253
262
        for res in self:
254
263
            try:
255
264
                result = res.validate()
256
 
            except ServerError as ex:
 
265
            except exception.Error as ex:
257
266
                logger.exception(ex)
258
267
                raise ex
259
268
            except Exception as ex:
328
337
        def resource_action(r):
329
338
            # Find e.g resource.create and call it
330
339
            action_l = action.lower()
331
 
            handle = getattr(r, '%s' % action_l, None)
332
 
            if callable(handle):
333
 
                return handle()
334
 
            else:
335
 
                raise exception.ResourceFailure(
336
 
                    AttributeError(_('Resource action %s not found') %
337
 
                                   action_l))
 
340
            handle = getattr(r, '%s' % action_l)
 
341
 
 
342
            return handle()
338
343
 
339
344
        action_task = scheduler.DependencyTaskGroup(self.dependencies,
340
345
                                                    resource_action,
354
359
        if callable(post_func):
355
360
            post_func()
356
361
 
357
 
    def update(self, newstack, action=UPDATE):
 
362
    def _backup_stack(self, create_if_missing=True):
 
363
        '''
 
364
        Get a Stack containing any in-progress resources from the previous
 
365
        stack state prior to an update.
 
366
        '''
 
367
        s = db_api.stack_get_by_name(self.context, self._backup_name(),
 
368
                                     owner_id=self.id)
 
369
        if s is not None:
 
370
            logger.debug('Loaded existing backup stack')
 
371
            return self.load(self.context, stack=s)
 
372
        elif create_if_missing:
 
373
            prev = type(self)(self.context, self.name, self.t, self.env,
 
374
                              owner_id=self.id)
 
375
            prev.store(backup=True)
 
376
            logger.debug('Created new backup stack')
 
377
            return prev
 
378
        else:
 
379
            return None
 
380
 
 
381
    def update(self, newstack):
358
382
        '''
359
383
        Compare the current stack with newstack,
360
384
        and where necessary create/update/delete the resources until
366
390
        Update will fail if it exceeds the specified timeout. The default is
367
391
        60 minutes, set in the constructor
368
392
        '''
 
393
        updater = scheduler.TaskRunner(self.update_task, newstack)
 
394
        updater()
 
395
 
 
396
    @scheduler.wrappertask
 
397
    def update_task(self, newstack, action=UPDATE):
369
398
        if action not in (self.UPDATE, self.ROLLBACK):
370
399
            logger.error("Unexpected action %s passed to update!" % action)
371
400
            self.state_set(self.UPDATE, self.FAILED,
381
410
                               'State invalid for %s' % action)
382
411
                return
383
412
 
384
 
        current_env = self.env
385
 
        self.env = newstack.env
386
 
        self.parameters = newstack.parameters
387
 
 
388
413
        self.state_set(self.UPDATE, self.IN_PROGRESS,
389
414
                       'Stack %s started' % action)
390
415
 
 
416
        oldstack = Stack(self.context, self.name, self.t, self.env)
 
417
        backup_stack = self._backup_stack()
 
418
 
391
419
        try:
392
 
            update_task = update.StackUpdate(self, newstack)
 
420
            update_task = update.StackUpdate(self, newstack, backup_stack,
 
421
                                             rollback=action == self.ROLLBACK)
393
422
            updater = scheduler.TaskRunner(update_task)
 
423
 
 
424
            self.env = newstack.env
 
425
            self.parameters = newstack.parameters
 
426
 
394
427
            try:
395
 
                updater(timeout=self.timeout_secs())
 
428
                updater.start(timeout=self.timeout_secs())
 
429
                yield
 
430
                while not updater.step():
 
431
                    yield
396
432
            finally:
397
433
                cur_deps = self._get_dependencies(self.resources.itervalues())
398
434
                self.dependencies = cur_deps
414
450
                # If rollback is enabled, we do another update, with the
415
451
                # existing template, so we roll back to the original state
416
452
                if not self.disable_rollback:
417
 
                    oldstack = Stack(self.context, self.name, self.t,
418
 
                                     current_env)
419
 
                    self.update(oldstack, action=self.ROLLBACK)
 
453
                    yield self.update_task(oldstack, action=self.ROLLBACK)
420
454
                    return
 
455
        else:
 
456
            logger.debug('Deleting backup stack')
 
457
            backup_stack.delete()
421
458
 
422
459
        self.state_set(action, stack_status, reason)
423
460
 
444
481
                           "Invalid action %s" % action)
445
482
            return
446
483
 
 
484
        stack_status = self.COMPLETE
 
485
        reason = 'Stack %s completed successfully' % action.lower()
447
486
        self.state_set(action, self.IN_PROGRESS, 'Stack %s started' % action)
448
487
 
449
 
        failures = []
450
 
        for res in reversed(self):
451
 
            try:
452
 
                res.destroy()
453
 
            except exception.ResourceFailure as ex:
454
 
                logger.error('Failed to delete %s error: %s' % (str(res),
455
 
                                                                str(ex)))
456
 
                failures.append(str(res))
457
 
 
458
 
        if failures:
459
 
            self.state_set(action, self.FAILED,
460
 
                           'Failed to %s : %s' % (action, ', '.join(failures)))
461
 
        else:
462
 
            self.state_set(action, self.COMPLETE, '%s completed' % action)
 
488
        backup_stack = self._backup_stack(False)
 
489
        if backup_stack is not None:
 
490
            backup_stack.delete()
 
491
            if backup_stack.status != backup_stack.COMPLETE:
 
492
                errs = backup_stack.status_reason
 
493
                failure = 'Error deleting backup resources: %s' % errs
 
494
                self.state_set(action, self.FAILED,
 
495
                               'Failed to %s : %s' % (action, failure))
 
496
                return
 
497
 
 
498
        action_task = scheduler.DependencyTaskGroup(self.dependencies,
 
499
                                                    resource.Resource.destroy,
 
500
                                                    reverse=True)
 
501
        try:
 
502
            scheduler.TaskRunner(action_task)(timeout=self.timeout_secs())
 
503
        except exception.ResourceFailure as ex:
 
504
            stack_status = self.FAILED
 
505
            reason = 'Resource %s failed: %s' % (action.lower(), str(ex))
 
506
        except scheduler.Timeout:
 
507
            stack_status = self.FAILED
 
508
            reason = '%s timed out' % action.title()
 
509
 
 
510
        self.state_set(action, stack_status, reason)
 
511
        if stack_status != self.FAILED:
463
512
            db_api.stack_delete(self.context, self.id)
464
513
            self.id = None
465
514
 
508
557
 
509
558
        for res in reversed(deps):
510
559
            try:
511
 
                res.destroy()
 
560
                scheduler.TaskRunner(res.destroy)()
512
561
            except exception.ResourceFailure as ex:
513
562
                failed = True
514
563
                logger.error('delete: %s' % str(ex))
572
621
                      functools.partial(template.resolve_attributes,
573
622
                                        resources=resources),
574
623
                      template.resolve_split,
 
624
                      template.resolve_member_list_to_map,
575
625
                      template.resolve_select,
576
626
                      template.resolve_joins,
577
627
                      template.resolve_replace,