@@ -64,6 +64,83 @@ def _reapply_allowed_hosts(lvol, snode, rpc_client):
6464 rpc_client .subsystem_add_host (lvol .nqn , host_entry ["nqn" ])
6565
6666
67+ def _set_lvol_ana_on_node (lvol , node , ana_state ):
68+ """Set ANA state for a single lvol's listeners on a given node."""
69+ rpc_client = RPCClient (node .mgmt_ip , node .rpc_port , node .rpc_username , node .rpc_password , timeout = 10 , retry = 2 )
70+ listener_port = node .get_lvol_subsys_port (lvol .lvs_name )
71+ for iface in node .data_nics :
72+ if iface .ip4_address and (lvol .fabric == iface .trtype .lower () or (lvol .fabric == "tcp" and node .active_tcp )):
73+ trtype = iface .trtype if lvol .fabric == iface .trtype .lower () else "TCP"
74+ ret = rpc_client .nvmf_subsystem_listener_set_ana_state (
75+ lvol .nqn , iface .ip4_address , listener_port , trtype = trtype , ana = ana_state )
76+ if not ret :
77+ logger .warning ("Failed to set ANA state %s for %s on %s" , ana_state , lvol .nqn , node .get_id ())
78+ else :
79+ logger .info ("ANA: %s on %s (%s) → %s" , lvol .nqn , node .get_id (), iface .ip4_address , ana_state )
80+
81+
82+ def _failover_primary_ana (primary_node ):
83+ """Primary failed: promote first_sec→optimized, second_sec→non_optimized."""
84+ db_controller = DBController ()
85+ lvol_list = [lv for lv in db_controller .get_lvols_by_node_id (primary_node .get_id ())
86+ if lv .status in [LVol .STATUS_ONLINE , LVol .STATUS_OFFLINE ]]
87+
88+ first_sec = None
89+ second_sec = None
90+ if primary_node .secondary_node_id :
91+ first_sec = db_controller .get_storage_node_by_id (primary_node .secondary_node_id )
92+ if primary_node .secondary_node_id_2 :
93+ second_sec = db_controller .get_storage_node_by_id (primary_node .secondary_node_id_2 )
94+
95+ for lvol in lvol_list :
96+ if first_sec and first_sec .status == StorageNode .STATUS_ONLINE :
97+ _set_lvol_ana_on_node (lvol , first_sec , "optimized" )
98+ if second_sec and second_sec .status == StorageNode .STATUS_ONLINE :
99+ _set_lvol_ana_on_node (lvol , second_sec , "non_optimized" )
100+
101+
102+ def _failback_primary_ana (primary_node ):
103+ """Primary restarting: second_sec→inaccessible, then first_sec→non_optimized."""
104+ db_controller = DBController ()
105+ lvol_list = [lv for lv in db_controller .get_lvols_by_node_id (primary_node .get_id ())
106+ if lv .status in [LVol .STATUS_ONLINE , LVol .STATUS_OFFLINE ]]
107+
108+ first_sec = None
109+ second_sec = None
110+ if primary_node .secondary_node_id :
111+ first_sec = db_controller .get_storage_node_by_id (primary_node .secondary_node_id )
112+ if primary_node .secondary_node_id_2 :
113+ second_sec = db_controller .get_storage_node_by_id (primary_node .secondary_node_id_2 )
114+
115+ # Order matters: demote second_sec FIRST, then first_sec
116+ for lvol in lvol_list :
117+ if second_sec and second_sec .status == StorageNode .STATUS_ONLINE :
118+ _set_lvol_ana_on_node (lvol , second_sec , "inaccessible" )
119+ for lvol in lvol_list :
120+ if first_sec and first_sec .status == StorageNode .STATUS_ONLINE :
121+ _set_lvol_ana_on_node (lvol , first_sec , "non_optimized" )
122+
123+
124+ def _failover_first_sec_ana (primary_node , second_sec_node ):
125+ """First sec failed: promote second_sec from inaccessible→non_optimized."""
126+ db_controller = DBController ()
127+ lvol_list = [lv for lv in db_controller .get_lvols_by_node_id (primary_node .get_id ())
128+ if lv .status in [LVol .STATUS_ONLINE , LVol .STATUS_OFFLINE ]]
129+
130+ for lvol in lvol_list :
131+ _set_lvol_ana_on_node (lvol , second_sec_node , "non_optimized" )
132+
133+
134+ def _failback_first_sec_ana (primary_node , second_sec_node ):
135+ """First sec restarting: demote second_sec from non_optimized→inaccessible."""
136+ db_controller = DBController ()
137+ lvol_list = [lv for lv in db_controller .get_lvols_by_node_id (primary_node .get_id ())
138+ if lv .status in [LVol .STATUS_ONLINE , LVol .STATUS_OFFLINE ]]
139+
140+ for lvol in lvol_list :
141+ _set_lvol_ana_on_node (lvol , second_sec_node , "inaccessible" )
142+
143+
67144def connect_device (name : str , device : NVMeDevice , node : StorageNode , bdev_names : List [str ], reattach : bool ):
68145 """Connect snode to device
69146
@@ -910,6 +987,21 @@ def _connect_to_remote_jm_devs(this_node, jm_ids=None):
910987 return new_devs
911988
912989
990+ def _refresh_cluster_maps_after_node_recovery (snode : StorageNode ):
991+ db_controller = DBController ()
992+ snode = db_controller .get_storage_node_by_id (snode .get_id ())
993+
994+ # Push a full cluster map after reconnect/restart recovery so peers do not
995+ # remain on stale per-device availability derived from transient reconnect state.
996+ distr_controller .send_cluster_map_to_node (snode )
997+
998+ for node in db_controller .get_storage_nodes_by_cluster_id (snode .cluster_id ):
999+ if node .get_id () == snode .get_id ():
1000+ continue
1001+ if node .status in [StorageNode .STATUS_ONLINE , StorageNode .STATUS_DOWN ]:
1002+ distr_controller .send_cluster_map_to_node (node )
1003+
1004+
9131005def ifc_is_tcp (nic ):
9141006 addrs = psutil .net_if_addrs ().get (nic , [])
9151007 for addr in addrs :
@@ -2119,6 +2211,7 @@ def restart_storage_node(
21192211 logger .info ("Cluster is not ready yet" )
21202212 logger .info ("Setting node status to Online" )
21212213 set_node_status (node_id , StorageNode .STATUS_ONLINE , reconnect_on_online = False )
2214+ _refresh_cluster_maps_after_node_recovery (snode )
21222215 return True
21232216
21242217 else :
@@ -2189,6 +2282,7 @@ def restart_storage_node(
21892282
21902283 logger .info ("Setting node status to Online" )
21912284 set_node_status (snode .get_id (), StorageNode .STATUS_ONLINE )
2285+ _refresh_cluster_maps_after_node_recovery (snode )
21922286
21932287 lvol_list = db_controller .get_lvols_by_node_id (snode .get_id ())
21942288 logger .info (f"Found { len (lvol_list )} lvols" )
@@ -2625,6 +2719,7 @@ def resume_storage_node(node_id):
26252719 if snode .enable_ha_jm :
26262720 snode .remote_jm_devices = _connect_to_remote_jm_devs (snode )
26272721 snode .write_to_db ()
2722+ _refresh_cluster_maps_after_node_recovery (snode )
26282723
26292724 fw_api = FirewallClient (snode , timeout = 20 , retry = 1 )
26302725 port_type = "tcp"
@@ -3286,6 +3381,14 @@ def recreate_lvstore_on_sec(secondary_node):
32863381
32873382 primary_lvs_port = primary_node .get_lvol_subsys_port (primary_node .lvstore )
32883383
3384+ # Failback ANA: if first secondary restarting, demote second secondary BEFORE blocking primary port
3385+ is_second_sec = (primary_node .secondary_node_id_2 == secondary_node .get_id ())
3386+ if not is_second_sec and primary_node .secondary_node_id_2 :
3387+ second_sec = db_controller .get_storage_node_by_id (primary_node .secondary_node_id_2 )
3388+ if second_sec and second_sec .status == StorageNode .STATUS_ONLINE :
3389+ if primary_node .status in [StorageNode .STATUS_ONLINE , StorageNode .STATUS_RESTARTING ]:
3390+ _failback_first_sec_ana (primary_node , second_sec )
3391+
32893392 if primary_node .status in [StorageNode .STATUS_ONLINE , StorageNode .STATUS_RESTARTING ]:
32903393 fw_api = FirewallClient (primary_node , timeout = 5 , retry = 2 )
32913394 ### 3- block primary port
@@ -3319,9 +3422,11 @@ def recreate_lvstore_on_sec(secondary_node):
33193422 tcp_ports_events .port_allowed (primary_node , primary_lvs_port )
33203423
33213424 ### 7- add lvols to subsystems
3425+ lvol_ana_state = "inaccessible" if is_second_sec else "non_optimized"
3426+
33223427 executor = ThreadPoolExecutor (max_workers = 50 )
33233428 for lvol in lvol_list :
3324- executor .submit (add_lvol_thread , lvol , secondary_node , lvol_ana_state = "non_optimized" )
3429+ executor .submit (add_lvol_thread , lvol , secondary_node , lvol_ana_state = lvol_ana_state )
33253430
33263431 primary_node = db_controller .get_storage_node_by_id (primary_node .get_id ())
33273432 primary_node .lvstore_status = "ready"
@@ -3410,6 +3515,11 @@ def recreate_lvstore(snode, force=False):
34103515 if lvol .allowed_hosts :
34113516 _reapply_allowed_hosts (lvol , snode , rpc_client )
34123517
3518+ # Failback ANA: demote secondaries BEFORE blocking ports
3519+ # Order: second_sec→inaccessible FIRST, then first_sec→non_optimized
3520+ if snode .secondary_node_id_2 and lvol_list :
3521+ _failback_primary_ana (snode )
3522+
34133523 snode_lvs_port = snode .get_lvol_subsys_port (snode .lvstore )
34143524 any_sec_unreachable = False
34153525 for sec_node in sec_nodes :
@@ -4406,4 +4516,4 @@ def lvs_dump_tree(node_id):
44064516 logger .error ("Failed to dump lvstore tree" )
44074517 return False
44084518
4409- return json .dumps (ret , indent = 2 )
4519+ return json .dumps (ret , indent = 2 )
0 commit comments