class PIDController(Controller): def __init__(self, app_id, plugin_info): self.logger = ScalingLog("pid.controller.log", "controller.log", app_id) self.app_id = app_id self.instances = plugin_info["instances"] self.check_interval = plugin_info["check_interval"] self.trigger_down = plugin_info["trigger_down"] self.trigger_up = plugin_info["trigger_up"] self.min_cap = plugin_info["min_cap"] self.max_cap = plugin_info["max_cap"] self.metric_rounding = plugin_info["metric_rounding"] self.actuator_type = plugin_info["actuator"] self.metric_source_type = plugin_info["metric_source"] self.heuristic_options = plugin_info["heuristic_options"] self.running = True self.running_lock = threading.RLock() metric_source = MetricSourceBuilder().get_metric_source( self.metric_source_type, plugin_info) actuator = ActuatorBuilder().get_actuator(self.actuator_type) self.alarm = PIDAlarm(actuator, metric_source, self.trigger_down, self.trigger_up, self.min_cap, self.max_cap, self.metric_rounding, self.heuristic_options, self.app_id, self.instances) def start_application_scaling(self): run = True while run: self.logger.log("Monitoring application: %s" % (self.app_id)) try: self.alarm.check_application_state() except MetricNotFoundException: self.logger.log("No metrics available") print "No metrics avaliable" except Exception as e: self.logger.log(str(e)) print "Unknown " + str(e) # Wait some time time.sleep(float(self.check_interval)) with self.running_lock: run = self.running def stop_application_scaling(self): with self.running_lock: self.running = False def status(self): return self.alarm.status()
class TestPIDAlarm(unittest.TestCase): def setUp(self): self.application_id_0 = "app-00" self.application_id_1 = "app-01" self.application_id_2 = "app-02" self.application_id_3 = "app-03" self.application_id_4 = "app-04" self.application_id_5 = "app-05" self.application_id_6 = "app-06" self.application_id_7 = "app-07" self.application_id_8 = "app-08" self.application_id_9 = "app-09" self.timestamp_1 = datetime.datetime.strptime("2000-01-01T00:00:00.0Z", "%Y-%m-%dT%H:%M:%S.%fZ") self.timestamp_2 = datetime.datetime.strptime("2000-01-01T00:05:00.0Z", "%Y-%m-%dT%H:%M:%S.%fZ") self.timestamp_3 = datetime.datetime.strptime("2000-01-01T00:10:00.0Z", "%Y-%m-%dT%H:%M:%S.%fZ") self.timestamp_4 = datetime.datetime.strptime("2000-01-01T00:15:00.0Z", "%Y-%m-%dT%H:%M:%S.%fZ") self.timestamps = [ self.timestamp_1, self.timestamp_2, self.timestamp_3, self.timestamp_4 ] self.instance_name_1 = "instance1" self.instance_name_2 = "instance2" self.instances = [self.instance_name_1, self.instance_name_2] self.trigger_down = 0.0 self.trigger_up = 0.0 self.min_cap = 10.0 self.max_cap = 100.0 self.actuation_size = 10.0 self.allocated_resources_scale_down = 100 self.allocated_resources_scale_up = 10 self.metric_round = 2 self.default_io_cap = 34 self.bigsea_username = "******" self.bigsea_password = "******" # self.authorization_url = "authorization_url" # self.authorization_data = dict( # authorization_url=self.authorization_url, # bigsea_username=self.bigsea_username, # bigsea_password=self.bigsea_password) compute_nodes = [] compute_nodes_key = "key" self.instances = [self.instance_name_1, self.instance_name_2] self.metric_source = MetricSourceBuilder().get_metric_source("nop", {}) self.instance_locator = InstanceLocator(SSHUtils({}), compute_nodes, compute_nodes_key) self.remote_kvm = RemoteKVM(SSHUtils({}), compute_nodes_key) self.actuator = KVMActuator( self.instance_locator, self.remote_kvm, # self.authorization_data, self.default_io_cap) self.proportional_factor = 1.5 self.derivative_factor = 0.5 self.integrative_factor = 1.5 self.heuristic_options = { "heuristic_name": "error_pid", "proportional_factor": self.proportional_factor, "derivative_factor": self.derivative_factor, "integrative_factor": self.integrative_factor } self.progress_error = { # CASE 1 self.application_id_0: { self.timestamp_1: -30.0, self.timestamp_2: -20.0, self.timestamp_3: -15.0 }, # CASE 2 self.application_id_1: { self.timestamp_1: -30.0, self.timestamp_2: -40.0, self.timestamp_3: -60.0 }, # CASE 3 self.application_id_2: { self.timestamp_1: 30.0, self.timestamp_2: 20.0, self.timestamp_3: 15.0 }, # CASE 4 self.application_id_3: { self.timestamp_1: 30.0, self.timestamp_2: 40.0, self.timestamp_3: 55.0 }, # CASE 5 self.application_id_4: { self.timestamp_1: 100.0, self.timestamp_2: 100.0, self.timestamp_3: 100.0 }, # CASE 6 self.application_id_5: { self.timestamp_1: -100.0, self.timestamp_2: -100.0, self.timestamp_3: -100.0 }, # CASE 7 self.application_id_6: { self.timestamp_1: -30.0, self.timestamp_2: 10.0, self.timestamp_3: 30.0 }, # CASE 8 self.application_id_7: { self.timestamp_1: -5.0, self.timestamp_2: -1.0, self.timestamp_3: 2.0 }, # CASE 9 self.application_id_8: { self.timestamp_1: -10.0, self.timestamp_2: -5.0, self.timestamp_3: 20.0, self.timestamp_4: -5.0 }, # CASE 10 self.application_id_9: { self.timestamp_1: -10.0, self.timestamp_2: 0.0, self.timestamp_3: 10.0, self.timestamp_4: 5.0 } } def metrics(self, metric_name, options): application_id = options["application_id"] timestamp_to_use = self.timestamps.pop(0) if metric_name == PIDAlarm.ERROR_METRIC_NAME: return timestamp_to_use,\ self.progress_error[application_id][timestamp_to_use] ''' CASE 1 The error is always negative and its absolute value decreases throughout the execution The derivative component decreases the proportional effect Uses application_id_0 progress ''' def test_alarm_gets_metrics_and_scales_up_decreasing_error(self): self.heuristic_options = { "heuristic_name": "error_pid", "proportional_factor": self.proportional_factor, "derivative_factor": self.derivative_factor, "integrative_factor": 0 } self.alarm = PIDAlarm(self.actuator, self.metric_source, self.trigger_down, self.trigger_up, self.min_cap, self.max_cap, self.metric_round, self.heuristic_options, self.application_id_0, self.instances) # # First call - there is no derivative effect - timestamp_1 # Proportional effect = 45 # Derivative effect = 0 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_0}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_up + 45 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Second call - timestamp_2 # Proportional effect = 30 # Derivative effect = -5 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_0}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_up + 25 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Third call - timestamp_3 # Proportional effect = 22.5 # Derivative effect = -2.5 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_0}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_up + 20 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) ''' CASE 2 The error is always negative and its absolute value increases throughout the execution The derivative component increases the proportional effect Uses application_id_1 progress ''' def test_alarm_gets_metrics_and_scales_up_increasing_error(self): # # First call - there is no derivative effect - timestamp_1 # Proportional effect = 45 # Derivative effect = 0 # self.heuristic_options = { "heuristic_name": "error_pid", "proportional_factor": self.proportional_factor, "derivative_factor": self.derivative_factor, "integrative_factor": 0 } self.alarm = PIDAlarm(self.actuator, self.metric_source, self.trigger_down, self.trigger_up, self.min_cap, self.max_cap, self.metric_round, self.heuristic_options, self.application_id_1, self.instances) # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_1}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_up + 45 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Second call - timestamp_2 # Proportional effect = 60 # Derivative effect = 5 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_1}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_up + 65 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Third call - timestamp_3 # Proportional effect = 90 # Derivative effect = 10 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_1}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_up + 90 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) ''' CASE 3 The error is always positive and its absolute value decreases throughout the execution The derivative component decreases the proportional effect Uses application_id_2 progress ''' def test_alarm_gets_metrics_and_scales_down_decreasing_error(self): # # First call - there is no derivative effect - timestamp_1 # Proportional effect = -45 # Derivative effect = 0 # self.heuristic_options = { "heuristic_name": "error_pid", "proportional_factor": self.proportional_factor, "derivative_factor": self.derivative_factor, "integrative_factor": 0 } self.alarm = PIDAlarm(self.actuator, self.metric_source, self.trigger_down, self.trigger_up, self.min_cap, self.max_cap, self.metric_round, self.heuristic_options, self.application_id_2, self.instances) # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_down) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_2}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_down - 45 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Second call - timestamp_2 # Proportional effect = -30 # Derivative effect = 5 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_down) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_2}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_down - 25 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Third call - timestamp_3 # Proportional effect = -22.5 # Derivative effect = 2.5 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_down) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_2}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_down - 20 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) ''' CASE 4 The error is always positive and its absolute value increases throughout the execution The derivative component increases the proportional effect Uses application_id_3 progress ''' def test_alarm_gets_metrics_and_scales_down_increasing_error(self): # # First call - there is no derivative effect - timestamp_1 # Proportional effect = -45 # Derivative effect = 0 # self.heuristic_options = { "heuristic_name": "error_pid", "proportional_factor": self.proportional_factor, "derivative_factor": self.derivative_factor, "integrative_factor": 0 } self.alarm = PIDAlarm(self.actuator, self.metric_source, self.trigger_down, self.trigger_up, self.min_cap, self.max_cap, self.metric_round, self.heuristic_options, self.application_id_3, self.instances) # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_down) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_3}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_down - 45 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Second call - timestamp_2 # Proportional effect = -60 # Derivative effect = -5 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_down) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_3}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_down - 65 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Third call - timestamp_3 # Proportional effect = -82.5 # Derivative effect = -7.5 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_down) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_3}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_down - 90 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) ''' CASE 5 The error value is set to force the use of the minimum possible cap value Uses application_id_4 progress ''' def test_alarm_scale_down_limits(self): # # First call - there is no derivative effect - timestamp_1 # self.heuristic_options = { "heuristic_name": "error_pid", "proportional_factor": self.proportional_factor, "derivative_factor": self.derivative_factor, "integrative_factor": 0 } self.alarm = PIDAlarm(self.actuator, self.metric_source, self.trigger_down, self.trigger_up, self.min_cap, self.max_cap, self.metric_round, self.heuristic_options, self.application_id_4, self.instances) self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_down) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_4}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.min_cap # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Second call - timestamp_2 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_down) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_4}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.min_cap # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) ''' CASE 6 The error value is set to force the use of the maximum possible cap value Uses application_id_5 progress ''' def test_alarm_scale_up_limits(self): # # First call - there is no derivative effect - timestamp_1 # self.heuristic_options = { "heuristic_name": "error_pid", "proportional_factor": self.proportional_factor, "derivative_factor": self.derivative_factor, "integrative_factor": 0 } self.alarm = PIDAlarm(self.actuator, self.metric_source, self.trigger_down, self.trigger_up, self.min_cap, self.max_cap, self.metric_round, self.heuristic_options, self.application_id_5, self.instances) self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_5}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.max_cap # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Second call - timestamp_2 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_5}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.max_cap # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) ''' CASE 7 The error is negative in the first call and positive in the following calls. Uses application_id_6 progress ''' def test_alarm_negative_to_positive_error(self): # # First call - there is no derivative effect - timestamp_1 # Proportional effect = 45 # Derivative effect = 0 self.heuristic_options = { "heuristic_name": "error_pid", "proportional_factor": self.proportional_factor, "derivative_factor": self.derivative_factor, "integrative_factor": 0 } self.alarm = PIDAlarm(self.actuator, self.metric_source, 0, 0, self.min_cap, self.max_cap, self.metric_round, self.heuristic_options, self.application_id_6, self.instances) self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_6}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_up + 45 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Second call - timestamp_2 # Proportional effect = -15 # Derivative effect = -20 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_down) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_6}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_down - 35 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Third call - timestamp_3 # Proportional effect = -45 # Derivative effect = -10 # self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_down) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_6}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_down - 55 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) ''' CASE 8 The error is negative in the two first calls and positive in the following calls. Uses application_id_7 progress ''' def test_alarm_smoothly_negative_to_positive_error(self): # # First call - there is no derivative effect - timestamp_1 # Proportional effect = 7.5 # Derivative effect = 0 # self.heuristic_options = { "heuristic_name": "error_pid", "proportional_factor": self.proportional_factor, "derivative_factor": self.derivative_factor, "integrative_factor": 0 } self.alarm = PIDAlarm(self.actuator, self.metric_source, 0, 0, self.min_cap, self.max_cap, self.metric_round, self.heuristic_options, self.application_id_7, self.instances) self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_7}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_up + 7.5 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Second call - timestamp_2 # Proportional effect = 1.5 # Derivative effect = -2.0 # # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_down) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_7}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_down - 0.5 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # # Third call - timestamp_3 # Proportional effect = -3.0 # Derivative effect = -1.5 # self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_down) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_7}) # The method tries to get the amount of allocated resources self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) new_cap = self.allocated_resources_scale_down - 4.5 # The method tries to adjust the amount of resources self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) ''' CASE 9 The proportional and derivative components are equal to zero. The error is negative in the two first calls, positive in the third and negative in the last one. Uses application_id_8 progress ''' def test_integrative_only(self): # First call self.proportional_factor = 0.0 self.derivative_factor = 0.0 self.allocated_resources_scale_up = 30 self.heuristic_options = { "heuristic_name": "error_pid", "proportional_factor": self.proportional_factor, "derivative_factor": self.derivative_factor, "integrative_factor": self.integrative_factor } self.alarm = PIDAlarm(self.actuator, self.metric_source, self.trigger_down, self.trigger_up, self.min_cap, self.max_cap, self.metric_round, self.heuristic_options, self.application_id_8, self.instances) # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_8}) self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) # Accumulated error is negative, therefore scale up new_cap = self.allocated_resources_scale_up + 15 self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # Second call # Set up mocks self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_8}) self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) # Accumulated error is negative, therefore scale up new_cap = self.allocated_resources_scale_up + 22.5 self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # Third call # Set up mocks self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_8}) self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) # Accumulated error is positive, therefore scale down new_cap = self.allocated_resources_scale_up - 7.5 self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # Fourth call # Set up mocks self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_8}) self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) # Accumulated error is zero, therefore do not scale new_cap = self.allocated_resources_scale_up self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) ''' CASE 10 - Tests interaction between the proportional, derivative and integrative algorithms Uses application_id_9 progress ''' def test_proportional_derivative_integrative(self): self.heuristic_options = { "heuristic_name": "error_pid", "proportional_factor": self.proportional_factor, "derivative_factor": self.derivative_factor, "integrative_factor": self.integrative_factor } self.alarm = PIDAlarm(self.actuator, self.metric_source, self.trigger_down, self.trigger_up, self.min_cap, self.max_cap, self.metric_round, self.heuristic_options, self.application_id_9, self.instances) # Set up mocks self.metric_source.get_most_recent_value = MagicMock() self.metric_source.get_most_recent_value.side_effect = self.metrics self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_9}) self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) proportional_component = 15 derivative_component = 0 integrative_component = 15 component_sum = proportional_component + \ derivative_component + integrative_component new_cap = min( max(self.allocated_resources_scale_up + component_sum, self.min_cap), self.max_cap) self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # Second call # Set up mocks self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_9}) self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) proportional_component = 0 derivative_component = -5 integrative_component = 15 component_sum = proportional_component + \ derivative_component + integrative_component new_cap = min( max(self.allocated_resources_scale_up + component_sum, self.min_cap), self.max_cap) self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # Third call # Set up mocks self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_9}) self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) proportional_component = -15 derivative_component = -5 integrative_component = 0 component_sum = proportional_component + \ derivative_component + integrative_component new_cap = min( max(self.allocated_resources_scale_up + component_sum, self.min_cap), self.max_cap) self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap }) # Fourth call # Set up mocks self.actuator.adjust_resources = MagicMock(return_value=None) self.actuator.get_allocated_resources_to_cluster = \ MagicMock(return_value=self.allocated_resources_scale_up) self.alarm.check_application_state() self.metric_source.get_most_recent_value.\ assert_any_call(PIDAlarm.ERROR_METRIC_NAME, {"application_id": self.application_id_9}) self.actuator.get_allocated_resources_to_cluster\ .assert_called_once_with( self.instances) proportional_component = -7.5 derivative_component = 2.5 integrative_component = -7.5 component_sum = proportional_component + \ derivative_component + integrative_component new_cap = min( max(self.allocated_resources_scale_up + component_sum, self.min_cap), self.max_cap) self.actuator.adjust_resources.assert_called_once_with({ self.instance_name_1: new_cap, self.instance_name_2: new_cap })