1+ import math
2+
13from naeural_core .main .net_mon import NetMonCt
24
35from extensions .business .deeploy .deeploy_const import (
@@ -73,6 +75,89 @@ def _parse_memory(self, mem):
7375 else :
7476 return int (float (mem )) # assume bytes
7577
78+ def _parse_node_telemetry_cpu (self , value ):
79+ """
80+ Parse node CPU telemetry. Missing or malformed node telemetry must fail
81+ closed as zero capacity instead of crashing deployment preflight.
82+ """
83+ try :
84+ cpu = self ._parse_cpu_value (value , default = 0.0 )
85+ except ValueError :
86+ return 0.0
87+ if cpu is None or not math .isfinite (cpu ) or cpu < 0 :
88+ return 0.0
89+ return cpu
90+
91+ def _parse_node_telemetry_memory_bytes (self , value ):
92+ """
93+ Parse node memory telemetry. NetMon reports memory in GB, but mixed-version
94+ or malformed summaries must fail closed as zero capacity, not abort deploy.
95+ """
96+ try :
97+ memory_bytes = self ._parse_memory (value )
98+ except (AttributeError , OverflowError , TypeError , ValueError ):
99+ return 0
100+ if not math .isfinite (memory_bytes ) or memory_bytes < 0 :
101+ return 0
102+ return int (memory_bytes )
103+
104+ def _parse_node_telemetry_bytes (self , value ):
105+ """
106+ Parse byte-valued node telemetry with the same fail-closed policy.
107+ """
108+ try :
109+ parsed = float (value )
110+ except (TypeError , ValueError ):
111+ return 0
112+ if not math .isfinite (parsed ) or parsed < 0 :
113+ return 0
114+ return int (parsed )
115+
116+ def _parse_node_telemetry_bool (self , value ):
117+ """
118+ Parse boolean node telemetry with a fail-closed policy. This protects
119+ Deeploy against mixed-version NetMon summaries returning "false"/"0" as
120+ strings, which Python would otherwise treat as truthy.
121+ """
122+ if isinstance (value , bool ):
123+ return value
124+ if isinstance (value , str ):
125+ value = value .strip ().lower ()
126+ if value in {"1" , "true" , "yes" , "y" }:
127+ return True
128+ if value in {"0" , "false" , "no" , "n" , "" }:
129+ return False
130+ return False
131+ if isinstance (value , (int , float )) and not isinstance (value , bool ):
132+ return math .isfinite (value ) and value == 1
133+ return False
134+
135+
136+ def _parse_node_supervisor_state (self , value ):
137+ """
138+ Parse supervisor telemetry for target selection.
139+
140+ Unlike generic capability booleans, unknown supervisor state must not be
141+ coerced to False: deploying to a supervisor is unsafe, so missing/malformed
142+ summary data is treated as "unknown" and filtered out by callers.
143+ """
144+ if isinstance (value , bool ):
145+ return value
146+ if isinstance (value , str ):
147+ value = value .strip ().lower ()
148+ if value == "" :
149+ return None
150+ if value in {"1" , "true" , "yes" , "y" }:
151+ return True
152+ if value in {"0" , "false" , "no" , "n" }:
153+ return False
154+ return None
155+ if isinstance (value , (int , float )) and not isinstance (value , bool ):
156+ if math .isfinite (value ) and value in (0 , 1 ):
157+ return bool (value )
158+ return None
159+ return None
160+
76161 def _get_request_plugin_signatures_from_pipeline (self , inputs ):
77162 """
78163 Extract plugin signatures from normalized request payload.
@@ -268,14 +353,16 @@ def __find_suitable_nodes_for_container_app(self, nodes_with_resources, containe
268353 self .Pd (f"Node { addr } projected usage (with new app): CPU={ used_cpu } cores, Memory={ used_memory } bytes" )
269354
270355 # Check if the node has enough resources
271- self .Pd (f"Node { addr } total resources: CPU={ node_resources ['cpu' ]} cores, Memory={ node_resources ['memory' ]} bytes" )
356+ node_cpu = self ._parse_node_telemetry_cpu (node_resources .get ('cpu' ))
357+ node_memory = self ._parse_node_telemetry_bytes (node_resources .get ('memory' ))
358+ self .Pd (f"Node { addr } total resources: CPU={ node_cpu } cores, Memory={ node_memory } bytes" )
272359 has_failed = False
273- if used_cpu > node_resources [ 'cpu' ] :
274- self .Pd (f"Node { addr } has not enough CPU cores. used_cpu ({ used_cpu } ) > node_cpu ({ node_resources [ 'cpu' ] } )" )
360+ if used_cpu > node_cpu :
361+ self .Pd (f"Node { addr } has not enough CPU cores. used_cpu ({ used_cpu } ) > node_cpu ({ node_cpu } )" )
275362 has_failed = True
276363
277- if used_memory > node_resources [ 'memory' ] :
278- self .Pd (f"Node { addr } has not enough RAM. used_memory ({ used_memory } ) > node_memory ({ node_resources [ 'memory' ] } )" )
364+ if used_memory > node_memory :
365+ self .Pd (f"Node { addr } has not enough RAM. used_memory ({ used_memory } ) > node_memory ({ node_memory } )" )
279366 has_failed = True
280367
281368 if has_failed :
@@ -371,7 +458,7 @@ def __check_nodes_capabilities_and_extract_resources(self, nodes: list['str'], i
371458 for addr in nodes :
372459 # Check if the node supports the requested plugin
373460 if requires_container_capabilities :
374- is_did_supported = self .netmon .network_node_has_did (addr = addr )
461+ is_did_supported = self ._parse_node_telemetry_bool ( self . netmon .network_node_has_did (addr = addr ) )
375462 if not is_did_supported :
376463 self .Pd (f"Node { addr } does not support container deployments. Skipping..." )
377464 continue
@@ -383,23 +470,24 @@ def __check_nodes_capabilities_and_extract_resources(self, nodes: list['str'], i
383470 continue
384471
385472 self .Pd (f"Node { addr } cont in function." )
386- total_cpu = self .netmon .network_node_total_cpu_cores (addr )
473+ total_cpu = self ._parse_node_telemetry_cpu ( self . netmon .network_node_total_cpu_cores (addr ) )
387474
388475 total_memory = self .netmon .network_node_total_mem (addr )
389- total_memory_bytes = self ._parse_memory (total_memory )
476+ total_memory_bytes = self ._parse_node_telemetry_memory_bytes (total_memory )
390477
391478 current_node_total_resources = {
392479 'cpu' : total_cpu ,
393480 'memory' : total_memory_bytes ,
394481 }
395482
396483 if node_res_req :
484+ node_req_cpu = self ._parse_cpu_value (node_req_cpu , default = None )
397485
398- if total_cpu < node_req_cpu :
486+ if node_req_cpu is not None and total_cpu < node_req_cpu :
399487 self .Pd (f"Node { addr } has not enough CPU cores in total. Skipping..." )
400488 continue
401489
402- if total_memory_bytes < node_req_memory_bytes :
490+ if node_req_memory_bytes > 0 and total_memory_bytes < node_req_memory_bytes :
403491 self .Pd (f"Node { addr } has not enought RAM in total. Skipping..." )
404492 continue
405493
@@ -426,8 +514,9 @@ def _find_nodes_for_deeployment(self, inputs):
426514 for addr , value in network_nodes .items ():
427515 ai_addr = self .bc .maybe_add_prefix (addr )
428516
429- is_online = self .netmon .network_node_is_online (ai_addr )
430- if value .get ('is_supervisor' ) is True or not is_online :
517+ is_online = self ._netmon_node_is_online_for_control (ai_addr )
518+ is_supervisor = self ._parse_node_supervisor_state (self .netmon .network_node_is_supervisor (ai_addr ))
519+ if is_supervisor is not False or not is_online :
431520 # FIXME: Disabled for now, as the most of the nodes are are marked as non-trusted.
432521 # if value.get('is_supervisor') is True or not value.get('trusted', False) or not is_online:
433522 continue
@@ -537,11 +626,11 @@ def _check_nodes_availability(self, inputs, skip_resource_check=False):
537626
538627 for node in inputs .target_nodes :
539628 addr = self ._check_and_maybe_convert_address (node )
540- is_supervisor = self .netmon .network_node_is_supervisor (addr = addr )
541- if is_supervisor :
542- msg = f"{ DEEPLOY_ERRORS .NODES6 } : Node { addr } is a supervisor node and cannot be used for deeployment"
629+ is_supervisor = self ._parse_node_supervisor_state ( self . netmon .network_node_is_supervisor (addr = addr ) )
630+ if is_supervisor is not False :
631+ msg = f"{ DEEPLOY_ERRORS .NODES6 } : Node { addr } is a supervisor node or has unknown supervisor state and cannot be used for deeployment"
543632 raise ValueError (msg )
544- is_online = self .netmon . network_node_is_online (addr )
633+ is_online = self ._netmon_node_is_online_for_control (addr )
545634 if is_online :
546635 if skip_resources :
547636 self .Pd (f"Skipping resource validation for node { addr } " )
@@ -588,15 +677,18 @@ def check_node_available_resources(self, addr, inputs):
588677 return result
589678
590679 # Get available resources
591- avail_cpu = self .netmon .network_node_get_cpu_avail_cores (addr )
680+ avail_cpu = self ._parse_node_telemetry_cpu ( self . netmon .network_node_get_cpu_avail_cores (addr ) )
592681 avail_mem = self .netmon .network_node_available_memory (addr ) # in GB
593- avail_mem_bytes = self ._parse_memory ( f" { avail_mem } g" )
594- avail_disk = self .netmon .network_node_available_disk (addr ) # in bytes
682+ avail_mem_bytes = self ._parse_node_telemetry_memory_bytes ( avail_mem )
683+ avail_disk = self ._parse_node_telemetry_bytes ( self . netmon .network_node_available_disk (addr ) ) # in bytes
595684
596685 # Get required resources from the request
597686 required_resources = self ._aggregate_container_resources (inputs ) or {}
598687 required_mem = required_resources .get (DEEPLOY_RESOURCES .MEMORY , DEFAULT_CONTAINER_RESOURCES .MEMORY )
599- required_cpu = required_resources .get (DEEPLOY_RESOURCES .CPU , DEFAULT_CONTAINER_RESOURCES .CPU )
688+ required_cpu = self ._parse_cpu_value (
689+ required_resources .get (DEEPLOY_RESOURCES .CPU , DEFAULT_CONTAINER_RESOURCES .CPU ),
690+ default = DEFAULT_CONTAINER_RESOURCES .CPU ,
691+ )
600692
601693 required_mem_bytes = self ._parse_memory (required_mem )
602694
0 commit comments