@@ -910,4 +910,284 @@ mod tests {
910910 // Even with high CPU, shouldn't scale beyond max
911911 // (This is tested by the scaling logic respecting max_instances)
912912 }
913+
914+ #[ tokio:: test]
915+ async fn test_autoscaler_memory_based_scaling ( ) {
916+ let registry = Arc :: new ( ComponentRegistry :: new ( ) ) ;
917+ let runtime_manager = Arc :: new ( RuntimeManager :: with_defaults ( ) ) ;
918+ let network_manager = Arc :: new ( super :: super :: NetworkManager :: default ( ) ) ;
919+ let vsock_manager = Arc :: new (
920+ super :: super :: VsockManager :: new ( std:: env:: temp_dir ( ) . join ( "osvm-memory-scale" ) )
921+ . unwrap ( ) ,
922+ ) ;
923+ let hotswap_manager = Arc :: new ( super :: super :: HotSwapManager :: new (
924+ runtime_manager. clone ( ) ,
925+ registry. clone ( ) ,
926+ Default :: default ( ) ,
927+ ) ) ;
928+
929+ let orchestrator = Arc :: new ( Orchestrator :: new (
930+ registry. clone ( ) ,
931+ runtime_manager,
932+ network_manager,
933+ vsock_manager,
934+ hotswap_manager,
935+ Default :: default ( ) ,
936+ ) ) ;
937+
938+ let autoscaler = AutoScaler :: new ( orchestrator, Default :: default ( ) ) ;
939+
940+ // Policy targeting memory usage
941+ let policy = ScalingPolicy {
942+ component_type : "MemoryIntensive" . to_string ( ) ,
943+ min_instances : 1 ,
944+ max_instances : 10 ,
945+ target_cpu : 0.70 ,
946+ target_memory : 0.75 , // 75% memory target
947+ scale_up_threshold : 0.10 ,
948+ scale_down_threshold : 0.10 ,
949+ scale_step : 2 , // Scale by 2 at a time
950+ target_rps : None ,
951+ } ;
952+
953+ autoscaler. add_policy ( policy) . await ;
954+
955+ // Register component with high memory usage
956+ let component = Component {
957+ id : ComponentId :: new ( ) ,
958+ component_type : ComponentType :: Service {
959+ name : "MemoryIntensive" . to_string ( ) ,
960+ } ,
961+ status : ComponentStatus :: Running ,
962+ isolation_config : IsolationConfig :: default ( ) ,
963+ runtime_handle : None ,
964+ metadata : Default :: default ( ) ,
965+ } ;
966+
967+ let component_id = component. id ;
968+ registry. register ( component) . await . unwrap ( ) ;
969+
970+ // Report high memory (90% > 75% + 10%)
971+ autoscaler
972+ . update_metrics ( ComponentMetrics {
973+ component_id,
974+ cpu_usage : 0.50 , // CPU is fine
975+ memory_usage : 0.90 , // Memory is high
976+ requests_per_sec : 100.0 ,
977+ avg_latency_ms : 50.0 ,
978+ timestamp : std:: time:: Instant :: now ( ) ,
979+ } )
980+ . await ;
981+
982+ // Should trigger scale up based on memory
983+ let result = autoscaler. evaluate_all_policies ( ) . await ;
984+ assert ! ( result. is_ok( ) ) ;
985+ }
986+
987+ #[ tokio:: test]
988+ async fn test_autoscaler_multiple_component_types ( ) {
989+ let registry = Arc :: new ( ComponentRegistry :: new ( ) ) ;
990+ let runtime_manager = Arc :: new ( RuntimeManager :: with_defaults ( ) ) ;
991+ let network_manager = Arc :: new ( super :: super :: NetworkManager :: default ( ) ) ;
992+ let vsock_manager = Arc :: new (
993+ super :: super :: VsockManager :: new ( std:: env:: temp_dir ( ) . join ( "osvm-multi-scale" ) ) . unwrap ( ) ,
994+ ) ;
995+ let hotswap_manager = Arc :: new ( super :: super :: HotSwapManager :: new (
996+ runtime_manager. clone ( ) ,
997+ registry. clone ( ) ,
998+ Default :: default ( ) ,
999+ ) ) ;
1000+
1001+ let orchestrator = Arc :: new ( Orchestrator :: new (
1002+ registry. clone ( ) ,
1003+ runtime_manager,
1004+ network_manager,
1005+ vsock_manager,
1006+ hotswap_manager,
1007+ Default :: default ( ) ,
1008+ ) ) ;
1009+
1010+ let autoscaler = AutoScaler :: new ( orchestrator, Default :: default ( ) ) ;
1011+
1012+ // Add policies for different component types
1013+ autoscaler
1014+ . add_policy ( ScalingPolicy {
1015+ component_type : "RpcNode" . to_string ( ) ,
1016+ min_instances : 2 ,
1017+ max_instances : 10 ,
1018+ target_cpu : 0.70 ,
1019+ target_memory : 0.80 ,
1020+ scale_up_threshold : 0.15 ,
1021+ scale_down_threshold : 0.20 ,
1022+ scale_step : 1 ,
1023+ target_rps : Some ( 1000.0 ) ,
1024+ } )
1025+ . await ;
1026+
1027+ autoscaler
1028+ . add_policy ( ScalingPolicy {
1029+ component_type : "Validator" . to_string ( ) ,
1030+ min_instances : 1 ,
1031+ max_instances : 3 ,
1032+ target_cpu : 0.80 ,
1033+ target_memory : 0.85 ,
1034+ scale_up_threshold : 0.10 ,
1035+ scale_down_threshold : 0.15 ,
1036+ scale_step : 1 ,
1037+ target_rps : None ,
1038+ } )
1039+ . await ;
1040+
1041+ // Register components of both types
1042+ let rpc = Component {
1043+ id : ComponentId :: new ( ) ,
1044+ component_type : ComponentType :: RpcNode {
1045+ network : "mainnet" . to_string ( ) ,
1046+ bind_address : None ,
1047+ } ,
1048+ status : ComponentStatus :: Running ,
1049+ isolation_config : IsolationConfig :: default ( ) ,
1050+ runtime_handle : None ,
1051+ metadata : Default :: default ( ) ,
1052+ } ;
1053+
1054+ let validator = Component {
1055+ id : ComponentId :: new ( ) ,
1056+ component_type : ComponentType :: Validator {
1057+ network : "mainnet" . to_string ( ) ,
1058+ identity : None ,
1059+ } ,
1060+ status : ComponentStatus :: Running ,
1061+ isolation_config : IsolationConfig :: default ( ) ,
1062+ runtime_handle : None ,
1063+ metadata : Default :: default ( ) ,
1064+ } ;
1065+
1066+ registry. register ( rpc. clone ( ) ) . await . unwrap ( ) ;
1067+ registry. register ( validator. clone ( ) ) . await . unwrap ( ) ;
1068+
1069+ // Report metrics for both
1070+ autoscaler
1071+ . update_metrics ( ComponentMetrics {
1072+ component_id : rpc. id ,
1073+ cpu_usage : 0.60 ,
1074+ memory_usage : 0.70 ,
1075+ requests_per_sec : 800.0 ,
1076+ avg_latency_ms : 45.0 ,
1077+ timestamp : std:: time:: Instant :: now ( ) ,
1078+ } )
1079+ . await ;
1080+
1081+ autoscaler
1082+ . update_metrics ( ComponentMetrics {
1083+ component_id : validator. id ,
1084+ cpu_usage : 0.75 ,
1085+ memory_usage : 0.80 ,
1086+ requests_per_sec : 0.0 ,
1087+ avg_latency_ms : 0.0 ,
1088+ timestamp : std:: time:: Instant :: now ( ) ,
1089+ } )
1090+ . await ;
1091+
1092+ // Evaluate all policies
1093+ let result = autoscaler. evaluate_all_policies ( ) . await ;
1094+ assert ! ( result. is_ok( ) ) ;
1095+ }
1096+
1097+ #[ test]
1098+ fn test_scaling_decision_logic ( ) {
1099+ let policy = ScalingPolicy {
1100+ component_type : "Test" . to_string ( ) ,
1101+ min_instances : 1 ,
1102+ max_instances : 10 ,
1103+ target_cpu : 0.70 ,
1104+ target_memory : 0.80 ,
1105+ scale_up_threshold : 0.10 ,
1106+ scale_down_threshold : 0.10 ,
1107+ scale_step : 2 ,
1108+ target_rps : None ,
1109+ } ;
1110+
1111+ // Test scale up scenario
1112+ assert_eq ! ( policy. scale_step, 2 ) ;
1113+ assert_eq ! ( policy. target_cpu, 0.70 ) ;
1114+
1115+ // Test thresholds
1116+ let high_cpu = 0.85 ; // 85% > 70% + 15% threshold
1117+ assert ! ( high_cpu > policy. target_cpu + policy. scale_up_threshold) ;
1118+
1119+ let low_cpu = 0.45 ; // 45% < 70% - 20% threshold
1120+ assert ! ( low_cpu < policy. target_cpu - policy. scale_down_threshold) ;
1121+ }
1122+
1123+ #[ tokio:: test]
1124+ async fn test_autoscaler_metrics_aggregation ( ) {
1125+ let registry = Arc :: new ( ComponentRegistry :: new ( ) ) ;
1126+ let runtime_manager = Arc :: new ( RuntimeManager :: with_defaults ( ) ) ;
1127+ let network_manager = Arc :: new ( super :: super :: NetworkManager :: default ( ) ) ;
1128+ let vsock_manager = Arc :: new (
1129+ super :: super :: VsockManager :: new ( std:: env:: temp_dir ( ) . join ( "osvm-metrics-agg" ) ) . unwrap ( ) ,
1130+ ) ;
1131+ let hotswap_manager = Arc :: new ( super :: super :: HotSwapManager :: new (
1132+ runtime_manager. clone ( ) ,
1133+ registry. clone ( ) ,
1134+ Default :: default ( ) ,
1135+ ) ) ;
1136+
1137+ let orchestrator = Arc :: new ( Orchestrator :: new (
1138+ registry. clone ( ) ,
1139+ runtime_manager,
1140+ network_manager,
1141+ vsock_manager,
1142+ hotswap_manager,
1143+ Default :: default ( ) ,
1144+ ) ) ;
1145+
1146+ let autoscaler = AutoScaler :: new ( orchestrator, Default :: default ( ) ) ;
1147+
1148+ // Register multiple components and report different metrics
1149+ let component_ids: Vec < _ > = ( 0 ..5 )
1150+ . map ( |i| {
1151+ let component = Component {
1152+ id : ComponentId :: new ( ) ,
1153+ component_type : ComponentType :: Service {
1154+ name : format ! ( "service-{}" , i) ,
1155+ } ,
1156+ status : ComponentStatus :: Running ,
1157+ isolation_config : IsolationConfig :: default ( ) ,
1158+ runtime_handle : None ,
1159+ metadata : Default :: default ( ) ,
1160+ } ;
1161+ let id = component. id ;
1162+ futures:: executor:: block_on ( registry. register ( component) ) . unwrap ( ) ;
1163+ id
1164+ } )
1165+ . collect ( ) ;
1166+
1167+ // Report varying metrics
1168+ for ( i, component_id) in component_ids. iter ( ) . enumerate ( ) {
1169+ autoscaler
1170+ . update_metrics ( ComponentMetrics {
1171+ component_id : * component_id,
1172+ cpu_usage : 0.60 + ( i as f64 * 0.05 ) , // 60%, 65%, 70%, 75%, 80%
1173+ memory_usage : 0.50 ,
1174+ requests_per_sec : 100.0 ,
1175+ avg_latency_ms : 50.0 ,
1176+ timestamp : std:: time:: Instant :: now ( ) ,
1177+ } )
1178+ . await ;
1179+ }
1180+
1181+ // Verify all metrics stored
1182+ let metrics = autoscaler. metrics . read ( ) . await ;
1183+ assert_eq ! ( metrics. len( ) , 5 ) ;
1184+
1185+ // Verify different CPU values
1186+ let cpu_values: Vec < f64 > = component_ids
1187+ . iter ( )
1188+ . filter_map ( |id| metrics. get ( id) . map ( |m| m. cpu_usage ) )
1189+ . collect ( ) ;
1190+ assert_eq ! ( cpu_values. len( ) , 5 ) ;
1191+ assert ! ( cpu_values[ 0 ] < cpu_values[ 4 ] ) ; // First < last
1192+ }
9131193}
0 commit comments