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 exception.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 exception.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] }) group.adjust(self.properties[self.SCALING_ADJUSTMENT], self.properties[self.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 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( exception.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=sc_util.CFN_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 exception.NoActionRequired() else: return capacity = grouputils.get_size(self) lower = self.properties[self.MIN_SIZE] upper = self.properties[self.MAX_SIZE] new_capacity = sc_util.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 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=exception.NoActionRequired()) mock_fin_scaling = self.patchobject(up_policy, '_finished_scaling') with mock.patch.object(up_policy, '_is_scaling_allowed', return_value=True) as mock_isa: self.assertRaises(exception.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 = exception.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()
def adjust(self, adjustment, adjustment_type=sc_util.CFN_CHANGE_IN_CAPACITY, min_adjustment_step=None): """Adjust the size of the scaling group if the cooldown permits.""" if self.status != self.COMPLETE: LOG.info( _LI("%s NOT performing scaling adjustment, " "when status is not COMPLETE") % self.name) raise exception.NoActionRequired() capacity = grouputils.get_size(self) new_capacity = self._get_new_capacity(capacity, adjustment, adjustment_type, min_adjustment_step) if new_capacity == capacity: LOG.info( _LI("%s NOT performing scaling adjustment, " "as there is no change in capacity.") % self.name) raise exception.NoActionRequired() if not self._is_scaling_allowed(): LOG.info( _LI("%(name)s NOT performing scaling adjustment, " "cooldown %(cooldown)s") % { 'name': self.name, 'cooldown': self.properties[self.COOLDOWN] }) raise exception.NoActionRequired() # 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', } size_changed = False try: 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: size_changed = True notif.update({ 'suffix': 'end', 'capacity': new_capacity, 'message': _("End resizing the group %(group)s") % { 'group': notif['groupname'] }, }) notification.send(**notif) except Exception: LOG.error( _LE("Error in performing scaling adjustment for" "group %s.") % self.name) raise finally: self._finished_scaling("%s : %s" % (adjustment_type, adjustment), size_changed=size_changed)
def handle_signal(self, details=None): # Template author can use scaling policy with any of the actions # of an alarm (i.e alarm_actions, insufficient_data_actions) and # it would be actioned irrespective of the alarm state. It's # fair to assume that the alarm state would be the appropriate one. # The responsibility of using a scaling policy with desired actions # lies with the template author, though this is normally expected to # be used with 'alarm_actions'. # # We also assume that the alarm state is 'alarm' when 'details' is None # or no 'current'/'state' key in 'details'. Watchrule has upper case # states, so we lower() them. This is only used for logging the alarm # state. 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 }) asgn_id = self.properties[self.AUTO_SCALING_GROUP_NAME] group = self.stack.resource_by_refid(asgn_id) if group is None: raise exception.NotFound( _('Alarm %(alarm)s could not find ' 'scaling group named "%(group)s"') % { 'alarm': self.name, 'group': asgn_id }) if not self._is_scaling_allowed(): LOG.info( _LI("%(name)s NOT performing scaling action, " "cooldown %(cooldown)s") % { 'name': self.name, 'cooldown': self.properties[self.COOLDOWN] }) raise exception.NoActionRequired() 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] }) size_changed = False try: group.adjust(self.properties[self.SCALING_ADJUSTMENT], self.properties[self.ADJUSTMENT_TYPE], self.properties[self.MIN_ADJUSTMENT_STEP]) size_changed = True except Exception as ex: if not isinstance(ex, exception.NoActionRequired): LOG.error( _LE("Error in performing scaling adjustment with " "%(name)s alarm for group %(group)s.") % { 'name': self.name, 'group': group.name }) raise finally: self._finished_scaling("%s : %s" % (self.properties[self.ADJUSTMENT_TYPE], self.properties[self.SCALING_ADJUSTMENT]), size_changed=size_changed)