def handle_signal(self, details=None): # ceilometer sends details like this: # {u'alarm_id': ID, u'previous': u'ok', u'current': u'alarm', # u'reason': u'...'}) # in this policy we currently assume that this gets called # only when there is an alarm. But the template writer can # put the policy in all the alarm notifiers (nodata, and ok). # # our watchrule has upper case states so lower() them all. if details is None: alarm_state = 'alarm' else: alarm_state = details.get('current', details.get('state', 'alarm')).lower() LOG.info(_LI('Alarm %(name)s, new state %(state)s'), { 'name': self.name, 'state': alarm_state }) if alarm_state != 'alarm': raise resource.NoActionRequired() if self._cooldown_inprogress(): LOG.info( _LI("%(name)s NOT performing scaling action, " "cooldown %(cooldown)s"), { 'name': self.name, 'cooldown': self.properties[self.COOLDOWN] }) raise resource.NoActionRequired() asgn_id = self.properties[self.AUTO_SCALING_GROUP_NAME] group = self.stack.resource_by_refid(asgn_id) try: if group is None: raise exception.NotFound( _('Alarm %(alarm)s could not find ' 'scaling group named "%(group)s"') % { 'alarm': self.name, 'group': asgn_id }) LOG.info( _LI('%(name)s Alarm, adjusting Group %(group)s with id ' '%(asgn_id)s by %(filter)s'), { 'name': self.name, 'group': group.name, 'asgn_id': asgn_id, 'filter': self.properties[self.SCALING_ADJUSTMENT] }) adjustment_type = self._get_adjustement_type() group.adjust(self.properties[self.SCALING_ADJUSTMENT], adjustment_type, self.properties[self.MIN_ADJUSTMENT_STEP], signal=True) finally: self._cooldown_timestamp( "%s : %s" % (self.properties[self.ADJUSTMENT_TYPE], self.properties[self.SCALING_ADJUSTMENT]))
def _check_scaling_allowed(self): metadata = self.metadata_get() # WRS: If heat-engine is killed after setting scaling_in_progress # and before clearing the flag, the cooldown is blocked forever. # scaling_date provides a way of triggering a cleanup later if metadata.get('scaling_in_progress'): sd = metadata.get('scaling_date', None) if sd is None: LOG.info( "Can not perform scaling action: resource %s " "is already in scaling.", self.name) reason = _('due to scaling activity') raise resource.NoActionRequired(res_name=self.name, reason=reason) scale_max_time = CONF.cooldown.scaling_wait_time if not timeutils.is_older_than(sd, scale_max_time): LOG.info( "Can not perform scaling action: resource %s " "is already in scaling.", self.name) reason = _('due to scaling activity') raise resource.NoActionRequired(res_name=self.name, reason=reason) try: # Negative values don't make sense, so they are clamped to zero cooldown = max(0, self.properties[self.COOLDOWN]) except TypeError: # If not specified, it will be None, same as cooldown == 0 cooldown = 0 if cooldown != 0: try: if 'cooldown' not in metadata: # Note: this is for supporting old version cooldown logic if metadata: last_adjust = next(six.iterkeys(metadata)) self._cooldown_check(cooldown, last_adjust) else: last_adjust = next(six.iterkeys(metadata['cooldown'])) self._cooldown_check(cooldown, last_adjust) except ValueError: # occurs when metadata has only {scaling_in_progress: False} pass # Assumes _finished_scaling is called # after the scaling operation completes metadata['scaling_in_progress'] = True metadata['scaling_date'] = timeutils.utcnow().isoformat() self.metadata_set(metadata)
def _check_scaling_allowed(self): metadata = self.metadata_get() if metadata.get('scaling_in_progress'): LOG.info( _LI("Can not perform scaling action: resource %s " "is already in scaling.") % self.name) reason = _('due to scaling activity') raise resource.NoActionRequired(res_name=self.name, reason=reason) try: # Negative values don't make sense, so they are clamped to zero cooldown = max(0, self.properties[self.COOLDOWN]) except TypeError: # If not specified, it will be None, same as cooldown == 0 cooldown = 0 if cooldown != 0: try: if 'cooldown' not in metadata: # Note: this is for supporting old version cooldown logic if metadata: last_adjust = next(six.iterkeys(metadata)) self._cooldown_check(cooldown, last_adjust) else: last_adjust = next(six.iterkeys(metadata['cooldown'])) self._cooldown_check(cooldown, last_adjust) except ValueError: # occurs when metadata has only {scaling_in_progress: False} pass # Assumes _finished_scaling is called # after the scaling operation completes metadata['scaling_in_progress'] = True self.metadata_set(metadata)
def _check_scaling_allowed(self, cooldown): metadata = self.metadata_get() if metadata.get('scaling_in_progress'): LOG.info( "Can not perform scaling action: resource %s " "is already in scaling.", self.name) reason = _('due to scaling activity') raise resource.NoActionRequired(res_name=self.name, reason=reason) cooldown = self._sanitize_cooldown(cooldown) # if both cooldown and cooldown_end not in metadata if all(k not in metadata for k in ('cooldown', 'cooldown_end')): # Note: this is for supporting old version cooldown checking metadata.pop('scaling_in_progress', None) if metadata and cooldown != 0: last_adjust = next(six.iterkeys(metadata)) if not timeutils.is_older_than(last_adjust, cooldown): self._log_and_raise_no_action(cooldown) elif 'cooldown_end' in metadata: cooldown_end = next(six.iterkeys(metadata['cooldown_end'])) now = timeutils.utcnow().isoformat() if now < cooldown_end: self._log_and_raise_no_action(cooldown) elif cooldown != 0: # Note: this is also for supporting old version cooldown checking last_adjust = next(six.iterkeys(metadata['cooldown'])) if not timeutils.is_older_than(last_adjust, cooldown): self._log_and_raise_no_action(cooldown) # Assumes _finished_scaling is called # after the scaling operation completes metadata['scaling_in_progress'] = True self.metadata_set(metadata)
def test_signal_no_action(self): test_d = { 'Data': 'foo', 'Reason': 'bar', 'Status': 'SUCCESS', 'UniqueId': '123' } self.stack = self.create_stack() self.stack.create() # mock a NoActionRequired from handle_signal() self.m.StubOutWithMock(generic_resource.SignalResource, 'handle_signal') generic_resource.SignalResource.handle_signal(test_d).AndRaise( resource.NoActionRequired()) # _add_event should not be called. self.m.StubOutWithMock(generic_resource.SignalResource, '_add_event') self.m.ReplayAll() rsrc = self.stack['signal_handler'] self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) self.assertTrue(rsrc.requires_deferred_auth) rsrc.signal(details=test_d) self.m.VerifyAll()
def adjust(self, adjustment, adjustment_type=CHANGE_IN_CAPACITY, min_adjustment_step=None, signal=False): """ Adjust the size of the scaling group if the cooldown permits. """ if self._cooldown_inprogress(): LOG.info(_LI("%(name)s NOT performing scaling adjustment, " "cooldown %(cooldown)s"), {'name': self.name, 'cooldown': self.properties[self.COOLDOWN]}) if signal: raise resource.NoActionRequired() else: return capacity = grouputils.get_size(self) lower = self.properties[self.MIN_SIZE] upper = self.properties[self.MAX_SIZE] new_capacity = _calculate_new_capacity(capacity, adjustment, adjustment_type, min_adjustment_step, lower, upper) # send a notification before, on-error and on-success. notif = { 'stack': self.stack, 'adjustment': adjustment, 'adjustment_type': adjustment_type, 'capacity': capacity, 'groupname': self.FnGetRefId(), 'message': _("Start resizing the group %(group)s") % { 'group': self.FnGetRefId()}, 'suffix': 'start', } notification.send(**notif) try: self.resize(new_capacity) except Exception as resize_ex: with excutils.save_and_reraise_exception(): try: notif.update({'suffix': 'error', 'message': six.text_type(resize_ex), 'capacity': grouputils.get_size(self), }) notification.send(**notif) except Exception: LOG.exception(_LE('Failed sending error notification')) else: notif.update({ 'suffix': 'end', 'capacity': new_capacity, 'message': _("End resizing the group %(group)s") % { 'group': notif['groupname']}, }) notification.send(**notif) finally: self._cooldown_timestamp("%s : %s" % (adjustment_type, adjustment))
def _log_and_raise_no_action(self, cooldown): LOG.info( "Can not perform scaling action: " "resource %(name)s is in cooldown (%(cooldown)s).", { 'name': self.name, 'cooldown': cooldown }) reason = _('due to cooldown, ' 'cooldown %s') % cooldown raise resource.NoActionRequired(res_name=self.name, reason=reason)
def test_scaling_policy_adjust_no_action(self): t = template_format.parse(as_template) stack = utils.parse_stack(t, params=as_params) up_policy = self.create_scaling_policy(t, stack, 'my-policy') group = stack['my-group'] self.patchobject(group, 'adjust', side_effect=resource.NoActionRequired()) self.assertRaises(resource.NoActionRequired, up_policy.handle_signal)
def _cooldown_check(self, cooldown, last_adjust): if not timeutils.is_older_than(last_adjust, cooldown): LOG.info( _LI("Can not perform scaling action: " "resource %(name)s is in cooldown (%(cooldown)s).") % { 'name': self.name, 'cooldown': cooldown }) reason = _('due to cooldown, ' 'cooldown %s') % cooldown raise resource.NoActionRequired(res_name=self.name, reason=reason)
def test_scaling_policy_adjust_no_action(self): t = template_format.parse(as_template) stack = utils.parse_stack(t, params=as_params) up_policy = self.create_scaling_policy(t, stack, 'my-policy') group = stack['my-group'] self.patchobject(group, 'adjust', side_effect=resource.NoActionRequired()) mock_fin_scaling = self.patchobject(up_policy, '_finished_scaling') with mock.patch.object(up_policy, '_check_scaling_allowed') as mock_isa: self.assertRaises(resource.NoActionRequired, up_policy.handle_signal) mock_isa.assert_called_once_with() mock_fin_scaling.assert_called_once_with('change_in_capacity : 1', size_changed=False)
def test_signal_no_action(self, mock_handle, mock_add): # Setup test_d = { 'Data': 'foo', 'Reason': 'bar', 'Status': 'SUCCESS', 'UniqueId': '123' } stack = self._create_stack(TEMPLATE_CFN_SIGNAL) mock_handle.side_effect = resource.NoActionRequired() rsrc = stack['signal_handler'] self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) self.assertTrue(rsrc.requires_deferred_auth) # Test mock_add.reset_mock() # clean up existing calls before test rsrc.signal(details=test_d) mock_handle.assert_called_once_with(test_d) mock_add.assert_not_called()