@@ -166,22 +166,24 @@ impl NodeGroupsPlugin {
166166 }
167167 } ) ;
168168
169- let plugin = Self {
169+ Self {
170170 configuration_templates : sorted_configs,
171171 store,
172172 store_context,
173173 node_groups_heartbeats,
174174 webhook_plugins,
175175 task_switching_policy,
176176 proximity_optimization_policy,
177- } ;
177+ }
178+ }
178179
179- plugin
180- . store_context
180+ /// Register this plugin as a task observer (async)
181+ pub async fn register_observer ( self : Arc < Self > ) -> Result < ( ) > {
182+ self . store_context
181183 . task_store
182- . add_observer ( Arc :: new ( plugin . clone ( ) ) ) ;
183-
184- plugin
184+ . add_observer ( self . clone ( ) )
185+ . await ;
186+ Ok ( ( ) )
185187 }
186188
187189 /// Check if a node is compatible with a configuration's compute requirements
@@ -776,19 +778,93 @@ impl NodeGroupsPlugin {
776778 let mut total_nodes = BTreeSet :: new ( ) ;
777779 let mut groups_to_dissolve = Vec :: new ( ) ;
778780
779- // Select groups for merging
780- for group in compatible_groups {
781- if total_nodes. len ( ) + group. nodes . len ( ) <= config. max_group_size {
782- merge_batch. push ( group. clone ( ) ) ;
783- total_nodes. extend ( group. nodes . iter ( ) . cloned ( ) ) ;
784- groups_to_dissolve. push ( group. id . clone ( ) ) ;
781+ // If proximity optimization is enabled, try to use location-based selection
782+ if self . proximity_optimization_policy . enabled {
783+ // Get node information for location data
784+ let nodes = self . store_context . node_store . get_nodes ( ) . await ?;
785+ let node_map: HashMap < String , & OrchestratorNode > = nodes
786+ . iter ( )
787+ . map ( |node| ( node. address . to_string ( ) , node) )
788+ . collect ( ) ;
789+
790+ // Try to find a seed group with location data
791+ let seed_group = compatible_groups. iter ( ) . find ( |group| {
792+ group
793+ . nodes
794+ . iter ( )
795+ . next ( )
796+ . and_then ( |addr| node_map. get ( addr) )
797+ . and_then ( |node| node. location . as_ref ( ) )
798+ . is_some ( )
799+ } ) ;
800+
801+ if let Some ( seed) = seed_group {
802+ // Found a seed with location, use proximity-based selection
803+ let seed_node = node_map. get ( seed. nodes . iter ( ) . next ( ) . unwrap ( ) ) . unwrap ( ) ;
785804
786- if total_nodes. len ( ) >= config. max_group_size {
787- break ;
805+ merge_batch. push ( seed. clone ( ) ) ;
806+ total_nodes. extend ( seed. nodes . iter ( ) . cloned ( ) ) ;
807+ groups_to_dissolve. push ( seed. id . clone ( ) ) ;
808+
809+ // Create a sorted list of remaining groups by proximity
810+ let mut remaining_with_distance: Vec < ( f64 , & NodeGroup ) > = compatible_groups
811+ . iter ( )
812+ . filter ( |g| g. id != seed. id )
813+ . filter_map ( |group| {
814+ let node_addr = group. nodes . iter ( ) . next ( ) ?;
815+ let node = node_map. get ( node_addr) ?;
816+ let node_loc = node. location . as_ref ( ) ?;
817+ let seed_loc = seed_node. location . as_ref ( ) ?;
818+ let distance = Self :: calculate_distance ( seed_loc, node_loc) ;
819+ Some ( ( distance, group) )
820+ } )
821+ . collect ( ) ;
822+
823+ remaining_with_distance
824+ . sort_by ( |a, b| a. 0 . partial_cmp ( & b. 0 ) . unwrap_or ( std:: cmp:: Ordering :: Equal ) ) ;
825+
826+ // Add closest groups first
827+ for ( _, group) in remaining_with_distance {
828+ if total_nodes. len ( ) + group. nodes . len ( ) <= config. max_group_size {
829+ merge_batch. push ( group. clone ( ) ) ;
830+ total_nodes. extend ( group. nodes . iter ( ) . cloned ( ) ) ;
831+ groups_to_dissolve. push ( group. id . clone ( ) ) ;
832+
833+ if total_nodes. len ( ) >= config. max_group_size {
834+ break ;
835+ }
836+ }
788837 }
789838 }
790839 }
791840
841+ // If no proximity-based selection happened or we still need more groups, use original logic
842+ if merge_batch. is_empty ( )
843+ || ( total_nodes. len ( ) < config. max_group_size
844+ && total_nodes. len ( ) < config. min_group_size )
845+ {
846+ // Reset if we didn't get enough nodes
847+ if total_nodes. len ( ) < config. min_group_size {
848+ merge_batch. clear ( ) ;
849+ total_nodes. clear ( ) ;
850+ groups_to_dissolve. clear ( ) ;
851+ }
852+
853+ // Original selection logic
854+ for group in compatible_groups {
855+ if !groups_to_dissolve. contains ( & group. id )
856+ && total_nodes. len ( ) + group. nodes . len ( ) <= config. max_group_size
857+ {
858+ merge_batch. push ( group. clone ( ) ) ;
859+ total_nodes. extend ( group. nodes . iter ( ) . cloned ( ) ) ;
860+ groups_to_dissolve. push ( group. id . clone ( ) ) ;
861+
862+ if total_nodes. len ( ) >= config. max_group_size {
863+ break ;
864+ }
865+ }
866+ }
867+ }
792868 // Validate merge conditions
793869 if !self
794870 . is_merge_beneficial ( & merge_batch, total_nodes. len ( ) )
0 commit comments