async def test_tune_everything_in_multiple_apps(self): interface = AsgardInterface() app1 = ScalableApp("test1") app2 = ScalableApp("test2") app3 = ScalableApp("test3") decisions = [ Decision(app1.id, cpu=0.2, mem=10), Decision(app2.id, cpu=0.4, mem=20), Decision(app3.id, cpu=0.1, mem=9), ] with aioresponses() as rsps: rsps.put( f"{settings.ASGARD_API_ADDRESS}/v2/apps", status=200, payload={ "deploymentId": "test2", "version": "1.0" }, ) applied_decisions = await interface.apply_decisions(decisions) self.assertEqual(len(applied_decisions), 3) for i in range(len(decisions)): self.assertEqual(applied_decisions[i]["id"], decisions[i].id) self.assertEqual(applied_decisions[i]["cpus"], decisions[i].cpu) self.assertEqual(applied_decisions[i]["mem"], decisions[i].mem)
async def test_cpu_decision_is_rounded_to_3_decimal_places(self): interface = AsgardInterface() decisions = [Decision("test", cpu=0.436_721_072_367)] body_fixture = [{"id": "test", "cpus": 0.437}] headers_fixture = { "Content-Type": "application/json", "Authorization": f"Token {settings.AUTOSCALER_AUTH_TOKEN}", } with aioresponses() as rsps: rsps.put( f"{settings.ASGARD_API_ADDRESS}/v2/apps", status=200, payload={ "deploymentId": "test", "version": "1.0" }, ) await interface.apply_decisions(decisions) calls = rsps.requests.get( ("put", URL(f"{settings.ASGARD_API_ADDRESS}/v2/apps"))) self.assertIsNotNone(calls) self.assertEqual(body_fixture, calls[0].kwargs.get("json")) self.assertEqual( headers_fixture, interface._asgard_client._http_client.default_headers, )
def decide_scaling_actions(self, apps: List[ScalableApp]) -> List[Decision]: decisions = [] for app in apps: if app.app_stats: decision = Decision(app.id) deploy_decision = False cpu_usage = app.app_stats.cpu_usage / 100 mem_usage = app.app_stats.mem_usage / 100 if app.is_set_to_scale_cpu(): if (cpu_usage > app.cpu_threshold + settings.AUTOSCALER_MARGIN_THRESHOLD or cpu_usage < app.cpu_threshold - settings.AUTOSCALER_MARGIN_THRESHOLD): new_cpu = (cpu_usage * app.cpu_allocated) / app.cpu_threshold decision.cpu = (app.min_cpu_scale_limit if new_cpu < app.min_cpu_scale_limit else app.max_cpu_scale_limit if new_cpu > app.max_cpu_scale_limit else new_cpu) deploy_decision = True if app.is_set_to_scale_mem(): if (mem_usage > app.mem_threshold + settings.AUTOSCALER_MARGIN_THRESHOLD or mem_usage < app.mem_threshold - settings.AUTOSCALER_MARGIN_THRESHOLD): new_mem = (mem_usage * app.mem_allocated) / app.mem_threshold decision.mem = (app.min_mem_scale_limit if new_mem < app.min_mem_scale_limit else app.max_mem_scale_limit if new_mem > app.max_mem_scale_limit else new_mem) deploy_decision = True if deploy_decision: decisions.append(decision) return decisions
async def test_tune_multiple_apps_with_different_params(self): interface = AsgardInterface() app1 = ScalableApp("test1") app2 = ScalableApp("test2") app3 = ScalableApp("test3") decisions = [ Decision(app1.id, mem=10), Decision(app2.id, cpu=0.4), Decision(app3.id, cpu=0.1, mem=9), ] with aioresponses() as rsps: rsps.put( f"{settings.ASGARD_API_ADDRESS}/v2/apps", status=200, payload={ "deploymentId": "test2", "version": "1.0" }, ) applied_decisions = await interface.apply_decisions(decisions) self.assertEqual(len(applied_decisions), 3) self.assertEqual(applied_decisions[0]["id"], decisions[0].id) self.assertEqual(applied_decisions[0]["mem"], decisions[0].mem) self.assertEqual("cpus" in applied_decisions[0], False) self.assertEqual(applied_decisions[1]["id"], decisions[1].id) self.assertEqual(applied_decisions[1]["cpus"], decisions[1].cpu) self.assertEqual("mem" in applied_decisions[1], False) self.assertEqual(applied_decisions[2]["id"], decisions[2].id) self.assertEqual(applied_decisions[2]["mem"], decisions[2].mem) self.assertEqual(applied_decisions[2]["cpus"], decisions[2].cpu)
async def test_tune_one_thing_in_one_app(self): interface = AsgardInterface() app = ScalableApp("test") decisions = [Decision(app.id, cpu=0.3)] with aioresponses() as rsps: rsps.put( f"{settings.ASGARD_API_ADDRESS}/v2/apps", status=200, payload={ "deploymentId": "test1", "version": "1.0" }, ) applied_decisions = await interface.apply_decisions(decisions) self.assertEqual(len(applied_decisions), 1) self.assertEqual(applied_decisions[0]["id"], decisions[0].id) self.assertEqual(applied_decisions[0]["cpus"], decisions[0].cpu) self.assertEqual("mem" in applied_decisions[0], False)
def decide_scaling_actions(self, apps: List[ScalableApp]) -> List[Decision]: decisions = [] for app in apps: if app.app_stats: decision = Decision(app.id) deploy_decision = False cpu_usage = app.app_stats.cpu_usage / 100 mem_usage = app.app_stats.mem_usage / 100 if app.is_set_to_scale_cpu(): if (abs(cpu_usage - app.cpu_threshold) > settings.AUTOSCALER_MARGIN_THRESHOLD): new_cpu = (cpu_usage * app.cpu_allocated) / app.cpu_threshold decision.cpu = (app.min_cpu_scale_limit if new_cpu < app.min_cpu_scale_limit else app.max_cpu_scale_limit if new_cpu > app.max_cpu_scale_limit else new_cpu) event = (DecisionEvents.CPU_SCALE_DOWN if app.cpu_allocated > decision.cpu else DecisionEvents.CPU_SCALE_UP) self.logger.info({ "appname": app.id, "event": event, "previous_value": app.cpu_allocated, "new_value": decision.cpu, }) deploy_decision = True else: self.logger.debug({ "appname": app.id, "event": DecisionEvents.CPU_SCALE_NONE, "reason": "usage within accepted margin", "usage": cpu_usage, "threshold": app.cpu_threshold, "accepted_margin": settings.AUTOSCALER_MARGIN_THRESHOLD, }) if app.is_set_to_scale_mem(): if (abs(mem_usage - app.mem_threshold) > settings.AUTOSCALER_MARGIN_THRESHOLD): new_mem = (mem_usage * app.mem_allocated) / app.mem_threshold decision.mem = (app.min_mem_scale_limit if new_mem < app.min_mem_scale_limit else app.max_mem_scale_limit if new_mem > app.max_mem_scale_limit else new_mem) event = (DecisionEvents.MEM_SCALE_DOWN if app.mem_allocated > decision.mem else DecisionEvents.MEM_SCALE_UP) self.logger.info({ "appname": app.id, "event": event, "previous_value": app.mem_allocated, "new_value": decision.mem, }) deploy_decision = True else: self.logger.debug({ "appname": app.id, "event": DecisionEvents.MEM_SCALE_NONE, "reason": "usage within accepted margin", "usage": mem_usage, "threshold": app.mem_threshold, "accepted_margin": settings.AUTOSCALER_MARGIN_THRESHOLD, }) if deploy_decision: decisions.append(decision) return decisions
def decide_scaling_actions(self, apps: List[ScalableApp]) -> List[Decision]: decisions = [] for app in apps: if app.app_stats: decision = Decision(app.id) if app.cpu_needs_scaling(): new_cpu = (app.get_cpu_usage() * app.cpu_allocated) / app.cpu_threshold new_cpu = _limit_number( new_cpu, app.min_cpu_scale_limit, app.max_cpu_scale_limit, ) if new_cpu != app.cpu_allocated: decision.cpu = new_cpu event = (DecisionEvents.CPU_SCALE_DOWN if app.cpu_allocated > decision.cpu else DecisionEvents.CPU_SCALE_UP) self.logger.info({ "appname": app.id, "event": event, "previous_value": app.cpu_allocated, "new_value": decision.cpu, }) if app.is_set_to_scale_cpu() and decision.cpu is None: self.logger.debug({ "appname": app.id, "event": DecisionEvents.CPU_SCALE_NONE, "reason": "usage within accepted margin", "usage": app.get_cpu_usage(), "threshold": app.cpu_threshold, "accepted_margin": settings.AUTOSCALER_MARGIN_THRESHOLD, }) if app.mem_needs_scaling(): new_mem = (app.get_mem_usage() * app.mem_allocated) / app.mem_threshold new_mem = _limit_number( new_mem, app.min_mem_scale_limit, app.max_mem_scale_limit, ) if new_mem != app.mem_allocated: decision.mem = new_mem event = (DecisionEvents.MEM_SCALE_DOWN if app.mem_allocated > decision.mem else DecisionEvents.MEM_SCALE_UP) self.logger.info({ "appname": app.id, "event": event, "previous_value": app.mem_allocated, "new_value": decision.mem, }) if app.is_set_to_scale_mem() and decision.mem is None: self.logger.debug({ "appname": app.id, "event": DecisionEvents.MEM_SCALE_NONE, "reason": "usage within accepted margin", "usage": app.get_mem_usage(), "threshold": app.mem_threshold, "accepted_margin": settings.AUTOSCALER_MARGIN_THRESHOLD, }) if decision.mem is not None or decision.cpu is not None: decisions.append(decision) return decisions