def test_wordpress_single_instance_stack_delete(self): ctx = create_context(self.m, tenant='test_delete_tenant') stack = get_wordpress_stack('test_stack', ctx) fc = setup_mocks(self.m, stack) self.m.ReplayAll() stack_id = stack.store() stack.create() db_s = db_api.stack_get(ctx, stack_id) self.assertNotEqual(db_s, None) self.assertNotEqual(stack.resources['WebServer'], None) self.assertTrue(stack.resources['WebServer'].resource_id > 0) self.m.StubOutWithMock(fc.client, 'get_servers_9999') get = fc.client.get_servers_9999 get().AndRaise(service.clients.novaclient.exceptions.NotFound(404)) mox.Replay(get) stack.delete() rsrc = stack.resources['WebServer'] self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state) self.assertEqual((stack.DELETE, stack.COMPLETE), rsrc.state) self.assertEqual(db_api.stack_get(ctx, stack_id), None) self.assertEqual(db_s.action, 'DELETE') self.assertEqual(db_s.status, 'COMPLETE')
def test_delete_badaction(self): self.stack = parser.Stack(self.ctx, 'delete_badaction_test', parser.Template({})) stack_id = self.stack.store() db_s = db_api.stack_get(self.ctx, stack_id) self.assertNotEqual(db_s, None) self.stack.delete(action="wibble") db_s = db_api.stack_get(self.ctx, stack_id) self.assertNotEqual(db_s, None) self.assertEqual(self.stack.state, self.stack.DELETE_FAILED)
def test_delete_rollback(self): self.stack = parser.Stack(self.ctx, 'delete_rollback_test', parser.Template({}), disable_rollback=False) stack_id = self.stack.store() db_s = db_api.stack_get(self.ctx, stack_id) self.assertNotEqual(db_s, None) self.stack.delete(action=self.stack.ROLLBACK) db_s = db_api.stack_get(self.ctx, stack_id) self.assertEqual(db_s, None) self.assertEqual(self.stack.state, self.stack.ROLLBACK_COMPLETE)
def test_delete(self): self.stack = parser.Stack(self.ctx, 'delete_test', parser.Template({})) stack_id = self.stack.store() db_s = db_api.stack_get(self.ctx, stack_id) self.assertNotEqual(db_s, None) self.stack.delete() db_s = db_api.stack_get(self.ctx, stack_id) self.assertEqual(db_s, None) self.assertEqual(self.stack.state, self.stack.DELETE_COMPLETE)
def _periodic_watcher_task(self, sid): """ Periodic task, created for each stack, triggers watch-rule evaluation for all rules defined for the stack sid = stack ID """ # Retrieve the stored credentials & create context # Require admin=True to the stack_get to defeat tenant # scoping otherwise we fail to retrieve the stack logger.debug("Periodic watcher task for stack %s" % sid) admin_context = context.get_admin_context() stack = db_api.stack_get(admin_context, sid, admin=True) if not stack: logger.error("Unable to retrieve stack %s for periodic task" % sid) return user_creds = db_api.user_creds_get(stack.user_creds_id) stack_context = context.RequestContext.from_dict(user_creds) # Get all watchrules for this stack and evaluate them try: wrs = db_api.watch_rule_get_all_by_stack(stack_context, sid) except Exception as ex: logger.warn('periodic_task db error (%s) %s' % ('watch rule removed?', str(ex))) return for wr in wrs: rule = watchrule.WatchRule.load(stack_context, watch=wr) actions = rule.evaluate() for action in actions: self._start_in_thread(sid, action)
def load(cls, context, stack_id=None, stack=None, resolve_data=True, parent_resource=None, show_deleted=True): '''Retrieve a Stack from the database.''' if stack is None: stack = db_api.stack_get(context, stack_id, show_deleted=show_deleted, eager_load=True) if stack is None: message = _('No stack exists with id "%s"') % str(stack_id) raise exception.NotFound(message) template = Template.load( context, stack.raw_template_id, stack.raw_template) env = environment.Environment(stack.parameters) stack = cls(context, stack.name, template, env, 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, created_time=stack.created_at, updated_time=stack.updated_at, user_creds_id=stack.user_creds_id, tenant_id=stack.tenant, validate_parameters=False) return stack
def load(cls, context, stack_id=None, stack=None, resolve_data=True, parent_resource=None, show_deleted=True): """Retrieve a Stack from the database.""" if stack is None: stack = db_api.stack_get(context, stack_id, show_deleted=show_deleted) if stack is None: message = _('No stack exists with id "%s"') % str(stack_id) raise exception.NotFound(message) template = Template.load(context, stack.raw_template_id) env = environment.Environment(stack.parameters) stack = cls( context, stack.name, template, env, stack.id, stack.action, stack.status, stack.status_reason, stack.timeout, resolve_data, stack.disable_rollback, parent_resource, owner_id=stack.owner_id, ) return stack
def test_stack_describe(self): self.m.StubOutWithMock(service.EngineService, '_get_stack') s = db_api.stack_get(self.ctx, self.stack.id) service.EngineService._get_stack(self.ctx, self.stack.identifier()).AndReturn(s) self.m.ReplayAll() sl = self.eng.show_stack(self.ctx, self.stack.identifier()) self.assertEqual(len(sl), 1) s = sl[0] self.assertTrue('creation_time' in s) self.assertTrue('updated_time' in s) self.assertTrue('stack_identity' in s) self.assertNotEqual(s['stack_identity'], None) self.assertTrue('stack_name' in s) self.assertEqual(s['stack_name'], self.stack.name) self.assertTrue('stack_status' in s) self.assertTrue('stack_status_reason' in s) self.assertTrue('description' in s) self.assertNotEqual(s['description'].find('WordPress'), -1) self.assertTrue('parameters' in s) self.m.VerifyAll()
def show_stack(self, context, stack_name, params): """ The show_stack method returns the attributes of one stack. arg1 -> RPC context. arg2 -> Name of the stack you want to see. arg3 -> Dict of http request parameters passed in from API side. """ res = {"stacks": []} s = db_api.stack_get(None, stack_name) if s: ps = parser.Stack(s.name, s.raw_template.parsed_template.template, s.id, params) mem = {} mem["stack_id"] = s.id mem["stack_name"] = s.name mem["creation_at"] = str(s.created_at) mem["updated_at"] = str(s.updated_at) mem["NotificationARNs"] = "TODO" mem["Parameters"] = ps.t["Parameters"] mem["StackStatusReason"] = "TODO" mem["TimeoutInMinutes"] = "TODO" mem["TemplateDescription"] = ps.t.get("Description", "No description") mem["StackStatus"] = ps.t.get("stack_status", "unknown") # only show the outputs on a completely created stack if ps.t["stack_status"] == ps.CREATE_COMPLETE: mem["Outputs"] = ps.get_outputs() res["stacks"].append(mem) return res
def test_nested_stack_update(self): urlfetch.get("https://localhost/the.template").MultipleTimes().AndReturn(self.nested_template) urlfetch.get("https://localhost/new.template").MultipleTimes().AndReturn(self.update_template) self.m.ReplayAll() stack = self.create_stack(self.test_template) rsrc = stack["the_nested"] original_nested_id = rsrc.resource_id t = template_format.parse(self.test_template) new_res = copy.deepcopy(t["Resources"]["the_nested"]) new_res["Properties"]["TemplateURL"] = "https://localhost/new.template" prop_diff = {"TemplateURL": "https://localhost/new.template"} rsrc.handle_update(new_res, {}, prop_diff) # Expect the physical resource name staying the same after update, # so that the nested was actually updated instead of replaced. self.assertEqual(original_nested_id, rsrc.resource_id) db_nested = db_api.stack_get(stack.context, rsrc.resource_id) # Owner_id should be preserved during the update process. self.assertEqual(stack.id, db_nested.owner_id) self.assertEqual("foo", rsrc.FnGetAtt("Outputs.Bar")) self.assertRaises(exception.InvalidTemplateAttribute, rsrc.FnGetAtt, "Foo") self.assertRaises(exception.InvalidTemplateAttribute, rsrc.FnGetAtt, "Outputs.Foo") self.assertRaises(exception.InvalidTemplateAttribute, rsrc.FnGetAtt, "Bar") rsrc.delete() self.m.VerifyAll()
def state_set(self, action, status, reason): '''Update the stack state in the database.''' if action not in self.ACTIONS: raise ValueError(_("Invalid action %s") % action) if status not in self.STATUSES: raise ValueError(_("Invalid status %s") % status) self.action = action self.status = status self.status_reason = reason if self.id is None: return stack = db_api.stack_get(self.context, self.id) if stack is not None: stack.update_and_save({'action': action, 'status': status, 'status_reason': reason}) msg = _('Stack %(action)s %(status)s (%(name)s): %(reason)s') LOG.info(msg % {'action': action, 'status': status, 'name': self.name, 'reason': reason}) notification.send(self)
def delete(self, action=DELETE, backup=False): ''' Delete all of the resources, and then the stack itself. The action parameter is used to differentiate between a user initiated delete and an automatic stack rollback after a failed create, which amount to the same thing, but the states are recorded differently. ''' if action not in (self.DELETE, self.ROLLBACK): logger.error(_("Unexpected action %s passed to delete!") % action) self.state_set(self.DELETE, self.FAILED, "Invalid action %s" % action) return stack_status = self.COMPLETE reason = 'Stack %s completed successfully' % action.lower() self.state_set(action, self.IN_PROGRESS, 'Stack %s started' % action) backup_stack = self._backup_stack(False) if backup_stack is not None: backup_stack.delete(backup=True) if backup_stack.status != backup_stack.COMPLETE: errs = backup_stack.status_reason failure = 'Error deleting backup resources: %s' % errs self.state_set(action, self.FAILED, 'Failed to %s : %s' % (action, failure)) return action_task = scheduler.DependencyTaskGroup(self.dependencies, resource.Resource.destroy, reverse=True) try: scheduler.TaskRunner(action_task)(timeout=self.timeout_secs()) except exception.ResourceFailure as ex: stack_status = self.FAILED reason = 'Resource %s failed: %s' % (action.lower(), str(ex)) except scheduler.Timeout: stack_status = self.FAILED reason = '%s timed out' % action.title() if stack_status != self.FAILED and not backup: # If we created a trust, delete it stack = db_api.stack_get(self.context, self.id) user_creds = db_api.user_creds_get(stack.user_creds_id) trust_id = user_creds.get('trust_id') if trust_id: try: self.clients.keystone().delete_trust(trust_id) except Exception as ex: logger.exception(ex) stack_status = self.FAILED reason = "Error deleting trust: %s" % str(ex) self.state_set(action, stack_status, reason) if stack_status != self.FAILED: # delete the stack db_api.stack_delete(self.context, self.id) self.id = None
def metadata_list_resources(self, context, stack_name): """ Return the resource IDs of the given stack. """ stack = db_api.stack_get(None, stack_name) if stack: return [r.name for r in stack.resources] else: return None
def refresh(self): db_stack = db_api.stack_get( self._context, self.id, show_deleted=True) db_stack.refresh() return self.__class__._from_db_object( self._context, self, db_stack )
def state_set(self, new_status, reason): """Update the stack state in the database""" self.state = new_status self.state_description = reason if self.id is None: return stack = db_api.stack_get(self.context, self.id) stack.update_and_save({"status": new_status, "status_reason": reason})
def load( cls, context, stack_id=None, stack=None, parent_resource=None, show_deleted=True, use_stored_context=False ): """Retrieve a Stack from the database.""" if stack is None: stack = db_api.stack_get(context, stack_id, show_deleted=show_deleted, eager_load=True) if stack is None: message = _('No stack exists with id "%s"') % str(stack_id) raise exception.NotFound(message) return cls._from_db(context, stack, parent_resource=parent_resource, use_stored_context=use_stored_context)
def refresh(self): db_stack = db_api.stack_get( self._context, self.id, show_deleted=True) if db_stack is None: message = _('No stack exists with id "%s"') % str(self.id) raise exception.NotFound(message) return self.__class__._from_db_object( self._context, self, db_stack )
def state_set(self, new_status, reason): '''Update the stack state in the database.''' self.state = new_status self.state_description = reason if self.id is None: return stack = db_api.stack_get(self.context, self.id) stack.update_and_save({'status': new_status, 'status_reason': reason})
def load(cls, context, stack_id): """Retrieve a Stack from the database""" s = db_api.stack_get(context, stack_id) if s is None: message = 'No stack exists with id "%s"' % str(stack_id) raise exception.NotFound(message) template = Template.load(context, s.raw_template_id) params = Parameters(s.name, template, s.parameters) stack = cls(context, s.name, template, params, stack_id, s.status, s.status_reason, s.timeout) return stack
def metadata_get_resource(self, context, stack_name, resource_id): """ Get the metadata for the given resource. """ s = db_api.stack_get(None, stack_name) if not s: return ["stack", None] template = s.raw_template.parsed_template.template if not resource_id in template.get("Resources", {}): return ["resource", None] metadata = template["Resources"][resource_id].get("Metadata", {}) return [None, metadata]
def _get_stack(self, cnxt, stack_identity): identity = identifier.HeatIdentifier(**stack_identity) if identity.tenant != cnxt.tenant_id: raise exception.InvalidTenant(target=identity.tenant, actual=cnxt.tenant_id) s = db_api.stack_get(cnxt, identity.stack_id) if s is None: raise exception.StackNotFound(stack_name=identity.stack_name) if identity.path or s.name != identity.stack_name: raise exception.StackNotFound(stack_name=identity.stack_name) return s
def _get_stack(self, context, stack_identity): identity = identifier.HeatIdentifier(**stack_identity) if identity.tenant != context.tenant: raise AttributeError('Invalid tenant') s = db_api.stack_get(context, identity.stack_id) if s is None: raise AttributeError('Stack not found') if identity.path or s.name != identity.stack_name: raise AttributeError('Invalid stack ID') return s
def test_stack_delete(self): stack_name = 'service_delete_test_stack' stack = get_wordpress_stack(stack_name, self.ctx) sid = stack.store() s = db_api.stack_get(self.ctx, sid) self.m.StubOutWithMock(parser.Stack, 'load') parser.Stack.load(self.ctx, stack=s).AndReturn(stack) self.m.ReplayAll() self.assertEqual(self.man.delete_stack(self.ctx, stack.identifier()), None) self.m.VerifyAll()
def load(cls, context, stack_id=None, stack=None, resolve_data=True): '''Retrieve a Stack from the database.''' if stack is None: stack = db_api.stack_get(context, stack_id) if stack is None: message = 'No stack exists with id "%s"' % str(stack_id) raise exception.NotFound(message) template = Template.load(context, stack.raw_template_id) params = Parameters(stack.name, template, stack.parameters) stack = cls(context, stack.name, template, params, stack.id, stack.status, stack.status_reason, stack.timeout, resolve_data, stack.disable_rollback) return stack
def test_stack_event_list(self): self.m.StubOutWithMock(service.EngineService, '_get_stack') s = db_api.stack_get(self.ctx, self.stack.id) service.EngineService._get_stack(self.ctx, self.stack.identifier()).AndReturn(s) self.m.ReplayAll() events = self.eng.list_events(self.ctx, self.stack.identifier()) self.assertEqual(len(events), 2) for ev in events: self.assertTrue('event_identity' in ev) self.assertEqual(type(ev['event_identity']), dict) self.assertTrue(ev['event_identity']['path'].rsplit('/', 1)[1]) self.assertTrue('logical_resource_id' in ev) self.assertEqual(ev['logical_resource_id'], 'WebServer') self.assertTrue('physical_resource_id' in ev) self.assertTrue('resource_properties' in ev) # Big long user data field.. it mentions 'wordpress' # a few times so this should work. user_data = ev['resource_properties']['UserData'] self.assertNotEqual(user_data.find('wordpress'), -1) self.assertEqual(ev['resource_properties']['ImageId'], 'F17-x86_64-gold') self.assertEqual(ev['resource_properties']['InstanceType'], 'm1.large') self.assertTrue('resource_action' in ev) self.assertTrue(ev['resource_status'] in ('IN_PROGRESS', 'CREATE_COMPLETE')) self.assertTrue('resource_status_reason' in ev) self.assertEqual(ev['resource_status_reason'], 'state changed') self.assertTrue('resource_type' in ev) self.assertEqual(ev['resource_type'], 'AWS::EC2::Instance') self.assertTrue('stack_identity' in ev) self.assertTrue('stack_name' in ev) self.assertEqual(ev['stack_name'], self.stack.name) self.assertTrue('event_time' in ev) self.m.VerifyAll()
def delete_stack(self, context, stack_name, params): """ The delete_stack method deletes a given stack. arg1 -> RPC context. arg2 -> Name of the stack you want to delete. arg3 -> Params passed from API. """ st = db_api.stack_get(None, stack_name) if not st: return {"Error": "No stack by that name"} logger.info("deleting stack %s" % stack_name) ps = parser.Stack(st.name, st.raw_template.parsed_template.template, st.id, params) ps.delete() return None
def list_events(self, context, stack_name): """ The list_events method lists all events associated with a given stack. arg1 -> RPC context. arg2 -> Name of the stack you want to get events for. """ if stack_name is not None: st = db_api.stack_get(None, stack_name) if not st: return {"Error": "No stack by that name"} events = db_api.event_get_all_by_stack(None, st.id) else: events = db_api.event_get_all(None) return {"events": [self.parse_event(e) for e in events]}
def identify_stack(self, cnxt, stack_name): """ The identify_stack method returns the full stack identifier for a single, live stack given the stack name. arg1 -> RPC context. arg2 -> Name or UUID of the stack to look up. """ if uuidutils.is_uuid_like(stack_name): s = db_api.stack_get(cnxt, stack_name, show_deleted=True) else: s = db_api.stack_get_by_name(cnxt, stack_name) if s: stack = parser.Stack.load(cnxt, stack=s) return dict(stack.identifier()) else: raise exception.StackNotFound(stack_name=stack_name)
def identify_stack(self, context, stack_name): """ The identify_stack method returns the full stack identifier for a single, live stack given the stack name. arg1 -> RPC context. arg2 -> Name or UUID of the stack to look up. """ if uuidutils.is_uuid_like(stack_name): s = db_api.stack_get(context, stack_name) else: s = db_api.stack_get_by_name(context, stack_name) if s: stack = parser.Stack.load(context, stack=s) return dict(stack.identifier()) else: raise AttributeError("Unknown stack name")
def create_stack(self, context, stack_name, template, params): """ The create_stack method creates a new stack using the template provided. Note that at this stage the template has already been fetched from the heat-api process if using a template-url. arg1 -> RPC context. arg2 -> Name of the stack you want to create. arg3 -> Template of stack you want to create. arg4 -> Params passed from API. """ logger.info("template is %s" % template) if db_api.stack_get(None, stack_name): return {"Error": "Stack already exists with that name."} metadata_server = config.FLAGS.heat_metadata_server_url # We don't want to reset the stack template, so we are making # an instance just for validation. template_copy = deepcopy(template) stack_validator = parser.Stack(stack_name, template_copy, 0, params, metadata_server=metadata_server) response = stack_validator.validate() stack_validator = None template_copy = None if "Malformed Query Response" in response["ValidateTemplateResult"]["Description"]: return response["ValidateTemplateResult"]["Description"] stack = parser.Stack(stack_name, template, 0, params, metadata_server=metadata_server) rt = {} rt["template"] = template rt["stack_name"] = stack_name new_rt = db_api.raw_template_create(None, rt) s = {} s["name"] = stack_name s["raw_template_id"] = new_rt.id new_s = db_api.stack_create(None, s) stack.id = new_s.id pt = {} pt["template"] = stack.t pt["raw_template_id"] = new_rt.id new_pt = db_api.parsed_template_create(None, pt) stack.parsed_template_id = new_pt.id stack.create() return {"stack": {"id": new_s.id, "name": new_s.name, "created_at": str(new_s.created_at)}}
def load(cls, context, stack_id=None, stack=None, resolve_data=True, parent_resource=None, show_deleted=True): '''Retrieve a Stack from the database.''' if stack is None: stack = db_api.stack_get(context, stack_id, show_deleted=show_deleted) if stack is None: message = _('No stack exists with id "%s"') % str(stack_id) raise exception.NotFound(message) template = Template.load(context, stack.raw_template_id) env = environment.Environment(stack.parameters) stack = cls(context, stack.name, template, env, stack.id, stack.action, stack.status, stack.status_reason, stack.timeout, resolve_data, stack.disable_rollback, parent_resource, owner_id=stack.owner_id) return stack
def _get_stack(self, cnxt, stack_identity, show_deleted=False): identity = identifier.HeatIdentifier(**stack_identity) if identity.tenant != cnxt.tenant_id: raise exception.InvalidTenant(target=identity.tenant, actual=cnxt.tenant_id) s = db_api.stack_get(cnxt, identity.stack_id, show_deleted=show_deleted) if s is None: raise exception.StackNotFound(stack_name=identity.stack_name) if identity.path or s.name != identity.stack_name: raise exception.StackNotFound(stack_name=identity.stack_name) return s
def rule_actions(self, new_state): logger.info('WATCH: stack:%s, watch_name:%s %s', self.stack_id, self.name, new_state) actions = [] if self.ACTION_MAP[new_state] not in self.rule: logger.info('no action for new state %s', new_state) else: s = db_api.stack_get(self.context, self.stack_id) stack = parser.Stack.load(self.context, stack=s) if (stack.action != stack.DELETE and stack.status == stack.COMPLETE): for refid in self.rule[self.ACTION_MAP[new_state]]: actions.append(stack.resource_by_refid(refid).signal) else: logger.warning("Could not process watch state %s for stack" % new_state) return actions
def check_stack_watches(self, sid): # Retrieve the stored credentials & create context # Require tenant_safe=False to the stack_get to defeat tenant # scoping otherwise we fail to retrieve the stack LOG.debug("Periodic watcher task for stack %s" % sid) admin_context = context.get_admin_context() db_stack = db_api.stack_get(admin_context, sid, tenant_safe=False, eager_load=True) if not db_stack: LOG.error(_LE("Unable to retrieve stack %s for periodic task"), sid) return stk = stack.Stack.load(admin_context, stack=db_stack, use_stored_context=True) # recurse into any nested stacks. children = db_api.stack_get_all_by_owner_id(admin_context, sid) for child in children: self.check_stack_watches(child.id) # Get all watchrules for this stack and evaluate them try: wrs = db_api.watch_rule_get_all_by_stack(admin_context, sid) except Exception as ex: LOG.warn(_LW('periodic_task db error watch rule removed? %(ex)s'), ex) return def run_alarm_action(stk, actions, details): for action in actions: action(details=details) for res in stk.itervalues(): res.metadata_update() for wr in wrs: rule = watchrule.WatchRule.load(stk.context, watch=wr) actions = rule.evaluate() if actions: self.thread_group_mgr.start(sid, run_alarm_action, stk, actions, rule.get_details())
def metadata_update(self, context, stack_id, resource_name, metadata): """ Update the metadata for the given resource. """ s = db_api.stack_get(None, stack_id) if s is None: logger.warn("Stack %s not found" % stack_id) return ['stack', None] stack = parser.Stack.load(None, stack=s) if resource_name not in stack: logger.warn("Resource not found %s:%s." % (stack_id, resource_name)) return ['resource', None] resource = stack[resource_name] resource.metadata = metadata return [None, resource.metadata]
def _get_stack(self, cnxt, stack_identity, show_deleted=False): identity = identifier.HeatIdentifier(**stack_identity) s = db_api.stack_get(cnxt, identity.stack_id, show_deleted=show_deleted) if s is None: raise exception.StackNotFound(stack_name=identity.stack_name) if cnxt.tenant_id not in (identity.tenant, s.stack_user_project_id): # The DB API should not allow this, but sanity-check anyway.. raise exception.InvalidTenant(target=identity.tenant, actual=cnxt.tenant_id) if identity.path or s.name != identity.stack_name: raise exception.StackNotFound(stack_name=identity.stack_name) return s
def check_stack_watches(self, sid): # Retrieve the stored credentials & create context # Require tenant_safe=False to the stack_get to defeat tenant # scoping otherwise we fail to retrieve the stack logger.debug(_("Periodic watcher task for stack %s") % sid) admin_context = context.get_admin_context() stack = db_api.stack_get(admin_context, sid, tenant_safe=False) if not stack: logger.error( _("Unable to retrieve stack %s for periodic task") % sid) return stack_context = EngineService.load_user_creds(stack.user_creds_id) # recurse into any nested stacks. children = db_api.stack_get_all_by_owner_id(admin_context, sid) for child in children: self.check_stack_watches(child.id) # Get all watchrules for this stack and evaluate them try: wrs = db_api.watch_rule_get_all_by_stack(stack_context, sid) except Exception as ex: logger.warn( _('periodic_task db error (%(msg)s) %(ex)s') % { 'msg': 'watch rule removed?', 'ex': str(ex) }) return def run_alarm_action(actions, details): for action in actions: action(details=details) stk = parser.Stack.load(stack_context, stack=stack) for res in stk.itervalues(): res.metadata_update() for wr in wrs: rule = watchrule.WatchRule.load(stack_context, watch=wr) actions = rule.evaluate() if actions: self.thread_group_mgr.start(sid, run_alarm_action, actions, rule.get_details())
def test_metadata(self): test_metadata = {'foo': 'bar', 'baz': 'quux', 'blarg': 'wibble'} pre_update_meta = self.stack['WebServer'].metadata self.m.StubOutWithMock(service.EngineService, '_get_stack') s = db_api.stack_get(self.ctx, self.stack.id) service.EngineService._get_stack(self.ctx, self.stack.identifier()).AndReturn(s) self.m.StubOutWithMock(instances.Instance, 'metadata_update') instances.Instance.metadata_update(new_metadata=test_metadata) self.m.ReplayAll() result = self.eng.metadata_update(self.ctx, dict(self.stack.identifier()), 'WebServer', test_metadata) # metadata_update is a no-op for all resources except # WaitConditionHandle so we don't expect this to have changed self.assertEqual(result, pre_update_meta) self.m.VerifyAll()
def state_set(self, action, status, reason): '''Update the stack state in the database.''' if action not in self.ACTIONS: raise ValueError(_("Invalid action %s") % action) if status not in self.STATUSES: raise ValueError(_("Invalid status %s") % status) self.action = action self.status = status self.status_reason = reason if self.id is None: return stack = db_api.stack_get(self.context, self.id) stack.update_and_save({'action': action, 'status': status, 'status_reason': reason}) notification.send(self)
def rule_action(self, new_state): logger.warn('WATCH: stack:%s, watch_name:%s %s', self.stack_id, self.name, new_state) actioned = False if not self.ACTION_MAP[new_state] in self.rule: logger.info('no action for new state %s', new_state) actioned = True else: s = db_api.stack_get(self.context, self.stack_id) if s and s.status in (parser.Stack.CREATE_COMPLETE, parser.Stack.UPDATE_COMPLETE): stack = parser.Stack.load(self.context, stack=s) for a in self.rule[self.ACTION_MAP[new_state]]: greenpool.spawn_n(stack[a].alarm) actioned = True else: logger.warning("Could not process watch state %s for stack" % new_state) return actioned
def load(cls, context, stack_id=None, stack=None, parent_resource=None, show_deleted=True, use_stored_context=False): '''Retrieve a Stack from the database.''' if stack is None: stack = db_api.stack_get(context, stack_id, show_deleted=show_deleted, eager_load=True) if stack is None: message = _('No stack exists with id "%s"') % str(stack_id) raise exception.NotFound(message) return cls._from_db(context, stack, parent_resource=parent_resource, use_stored_context=use_stored_context)
def rule_actions(self, new_state): LOG.info( _LI('WATCH: stack:%(stack)s, watch_name:%(watch_name)s, ' 'new_state:%(new_state)s'), { 'stack': self.stack_id, 'watch_name': self.name, 'new_state': new_state }) actions = [] if self.ACTION_MAP[new_state] not in self.rule: LOG.info(_LI('no action for new state %s'), new_state) else: s = db_api.stack_get(self.context, self.stack_id, eager_load=True) stk = stack.Stack.load(self.context, stack=s) if (stk.action != stk.DELETE and stk.status == stk.COMPLETE): for refid in self.rule[self.ACTION_MAP[new_state]]: actions.append(stk.resource_by_refid(refid).signal) else: LOG.warn(_LW("Could not process watch state %s for stack"), new_state) return actions
def identify_stack(self, cnxt, stack_name): """ The identify_stack method returns the full stack identifier for a single, live stack given the stack name. :param cnxt: RPC context. :param stack_name: Name or UUID of the stack to look up. """ if uuidutils.is_uuid_like(stack_name): s = db_api.stack_get(cnxt, stack_name, show_deleted=True) # may be the name is in uuid format, so if get by id returns None, # we should get the info by name again if not s: s = db_api.stack_get_by_name(cnxt, stack_name) else: s = db_api.stack_get_by_name(cnxt, stack_name) if s: stack = parser.Stack.load(cnxt, stack=s) return dict(stack.identifier()) else: raise exception.StackNotFound(stack_name=stack_name)
def test_nested_stack_update(self): urlfetch.get('https://server.test/the.template').MultipleTimes().\ AndReturn(self.nested_template) urlfetch.get('https://server.test/new.template').MultipleTimes().\ AndReturn(self.update_template) self.m.ReplayAll() stack = self.create_stack(self.test_template) rsrc = stack['the_nested'] original_nested_id = rsrc.resource_id t = template_format.parse(self.test_template) new_res = copy.deepcopy(t['Resources']['the_nested']) new_res['Properties']['TemplateURL'] = ( 'https://server.test/new.template') prop_diff = {'TemplateURL': 'https://server.test/new.template'} updater = rsrc.handle_update(new_res, {}, prop_diff) updater.run_to_completion() self.assertIs(True, rsrc.check_update_complete(updater)) # Expect the physical resource name staying the same after update, # so that the nested was actually updated instead of replaced. self.assertEqual(original_nested_id, rsrc.resource_id) db_nested = db_api.stack_get(stack.context, rsrc.resource_id) # Owner_id should be preserved during the update process. self.assertEqual(stack.id, db_nested.owner_id) self.assertEqual('foo', rsrc.FnGetAtt('Outputs.Bar')) self.assertRaises( exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Foo') self.assertRaises( exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Outputs.Foo') self.assertRaises( exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Bar') rsrc.delete() self.m.VerifyAll()
def _periodic_watcher_task(self, sid): """ Periodic task, created for each stack, triggers watch-rule evaluation for all rules defined for the stack sid = stack ID """ # Retrieve the stored credentials & create context # Require admin=True to the stack_get to defeat tenant # scoping otherwise we fail to retrieve the stack logger.debug("Periodic watcher task for stack %s" % sid) admin_context = context.get_admin_context() stack = db_api.stack_get(admin_context, sid, admin=True) if not stack: logger.error("Unable to retrieve stack %s for periodic task" % sid) return user_creds = db_api.user_creds_get(stack.user_creds_id) stack_context = context.RequestContext.from_dict(user_creds) # Get all watchrules for this stack and evaluate them try: wrs = db_api.watch_rule_get_all_by_stack(stack_context, sid) except Exception as ex: logger.warn('periodic_task db error (%s) %s' % ('watch rule removed?', str(ex))) return def run_alarm_action(actions, details): for action in actions: action(details=details) stk = parser.Stack.load(stack_context, stack=stack) for res in stk: res.metadata_update() for wr in wrs: rule = watchrule.WatchRule.load(stack_context, watch=wr) actions = rule.evaluate() if actions: self._start_in_thread(sid, run_alarm_action, actions, rule.get_details())
def _check_stack_watches(self, sid): # Retrieve the stored credentials & create context # Require admin=True to the stack_get to defeat tenant # scoping otherwise we fail to retrieve the stack logger.debug("Periodic watcher task for stack %s" % sid) admin_context = context.get_admin_context() stack = db_api.stack_get(admin_context, sid, admin=True) if not stack: logger.error("Unable to retrieve stack %s for periodic task" % sid) return stack_context = self._load_user_creds(stack.user_creds_id) # recurse into any nested stacks. children = db_api.stack_get_all_by_owner_id(admin_context, sid) for child in children: self._check_stack_watches(child.id) # Get all watchrules for this stack and evaluate them try: wrs = db_api.watch_rule_get_all_by_stack(stack_context, sid) except Exception as ex: logger.warn('periodic_task db error (%s) %s' % ('watch rule removed?', str(ex))) return def run_alarm_action(actions, details): for action in actions: action(details=details) stk = parser.Stack.load(stack_context, stack=stack) for res in stk: res.metadata_update() for wr in wrs: rule = watchrule.WatchRule.load(stack_context, watch=wr) actions = rule.evaluate() if actions: self._start_in_thread(sid, run_alarm_action, actions, rule.get_details())
def test_stack_update(self): stack_name = 'service_update_test_stack' params = {'foo': 'bar'} template = '{ "Template": "data" }' old_stack = get_wordpress_stack(stack_name, self.ctx) sid = old_stack.store() s = db_api.stack_get(self.ctx, sid) stack = get_wordpress_stack(stack_name, self.ctx) self.m.StubOutWithMock(parser, 'Stack') self.m.StubOutWithMock(parser.Stack, 'load') parser.Stack.load(self.ctx, stack=s).AndReturn(old_stack) self.m.StubOutWithMock(parser, 'Template') self.m.StubOutWithMock(parser, 'Parameters') parser.Template(template).AndReturn(stack.t) parser.Parameters(stack_name, stack.t, params).AndReturn(stack.parameters) parser.Stack(self.ctx, stack.name, stack.t, stack.parameters).AndReturn(stack) self.m.StubOutWithMock(stack, 'validate') stack.validate().AndReturn(None) self.m.StubOutWithMock(threadgroup, 'ThreadGroup') threadgroup.ThreadGroup().AndReturn(DummyThreadGroup()) self.m.ReplayAll() result = self.man.update_stack(self.ctx, old_stack.identifier(), template, params, {}) self.assertEqual(result, old_stack.identifier()) self.assertTrue(isinstance(result, dict)) self.assertTrue(result['stack_id']) self.m.VerifyAll()
def get_by_id(cls, context, stack_id, **kwargs): db_stack = db_api.stack_get(context, stack_id, **kwargs) if not db_stack: return None stack = cls._from_db_object(context, cls(context), db_stack) return stack
def refresh(self): db_stack = db_api.stack_get(self._context, self.id, show_deleted=True) if db_stack is None: message = _('No stack exists with id "%s"') % str(self.id) raise exception.NotFound(message) return self.__class__._from_db_object(self._context, self, db_stack)
def refresh(self): db_stack = db_api.stack_get(self._context, self.id, show_deleted=True) db_stack.refresh() return self.__class__._from_db_object(self._context, self, db_stack)
def delete(self, action=DELETE, backup=False): ''' Delete all of the resources, and then the stack itself. The action parameter is used to differentiate between a user initiated delete and an automatic stack rollback after a failed create, which amount to the same thing, but the states are recorded differently. ''' if action not in (self.DELETE, self.ROLLBACK): logger.error(_("Unexpected action %s passed to delete!") % action) self.state_set(self.DELETE, self.FAILED, "Invalid action %s" % action) return stack_status = self.COMPLETE reason = 'Stack %s completed successfully' % action self.state_set(action, self.IN_PROGRESS, 'Stack %s started' % action) backup_stack = self._backup_stack(False) if backup_stack is not None: backup_stack.delete(backup=True) if backup_stack.status != backup_stack.COMPLETE: errs = backup_stack.status_reason failure = 'Error deleting backup resources: %s' % errs self.state_set(action, self.FAILED, 'Failed to %s : %s' % (action, failure)) return action_task = scheduler.DependencyTaskGroup(self.dependencies, resource.Resource.destroy, reverse=True) try: scheduler.TaskRunner(action_task)(timeout=self.timeout_secs()) except exception.ResourceFailure as ex: stack_status = self.FAILED reason = 'Resource %s failed: %s' % (action, str(ex)) except scheduler.Timeout: stack_status = self.FAILED reason = '%s timed out' % action.title() if stack_status != self.FAILED and not backup: # If we created a trust, delete it stack = db_api.stack_get(self.context, self.id) user_creds = db_api.user_creds_get(stack.user_creds_id) trust_id = user_creds.get('trust_id') if trust_id: try: self.clients.keystone().delete_trust(trust_id) except Exception as ex: logger.exception(ex) stack_status = self.FAILED reason = "Error deleting trust: %s" % str(ex) # If the stack has a domain project, delete it if self.stack_user_project_id: try: self.clients.keystone().delete_stack_domain_project( project_id=self.stack_user_project_id) except Exception as ex: logger.exception(ex) stack_status = self.FAILED reason = "Error deleting project: %s" % str(ex) self.state_set(action, stack_status, reason) if stack_status != self.FAILED: # delete the stack db_api.stack_delete(self.context, self.id) self.id = None