Skip to content

Commit 8905c0d

Browse files
Merge branch 'feature-lvol-migration' into main
2 parents 0f9bb43 + bd7e591 commit 8905c0d

File tree

3 files changed

+139
-4
lines changed

3 files changed

+139
-4
lines changed

simplyblock_core/controllers/lvol_controller.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -784,9 +784,12 @@ def add_lvol_on_node(lvol, snode, is_primary=True, secondary_index=0):
784784
else:
785785
rpc_client.subsystem_add_host(lvol.nqn, host_entry["nqn"])
786786

787-
ana_state = "non_optimized"
788-
if lvol.node_id == snode.get_id():
787+
if is_primary or lvol.node_id == snode.get_id():
789788
ana_state = "optimized"
789+
elif secondary_index >= 1:
790+
ana_state = "inaccessible"
791+
else:
792+
ana_state = "non_optimized"
790793

791794
# add listeners
792795
# Use the per-lvstore port for the lvol's lvstore

simplyblock_core/services/storage_node_monitor.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,28 @@ def set_node_unreachable(node):
239239
try:
240240
storage_node_ops.set_node_status(node.get_id(), StorageNode.STATUS_UNREACHABLE)
241241
update_cluster_status(cluster_id)
242+
243+
# ANA tiering failover for FT=2
244+
# Case 1: Primary node failed — promote first_sec→optimized, second_sec→non_optimized
245+
if not node.is_secondary_node and node.secondary_node_id_2:
246+
try:
247+
storage_node_ops._failover_primary_ana(node)
248+
except Exception as ana_e:
249+
logger.error("Failover ANA for primary %s failed: %s", node.get_id(), ana_e)
250+
251+
# Case 2: First secondary failed — promote second_sec from inaccessible→non_optimized
252+
if node.is_secondary_node:
253+
for primary_node in db.get_primary_storage_nodes_by_secondary_node_id(node.get_id()):
254+
# Only if this node is the first secondary (not the second)
255+
if primary_node.secondary_node_id == node.get_id() and primary_node.secondary_node_id_2:
256+
second_sec = db.get_storage_node_by_id(primary_node.secondary_node_id_2)
257+
if second_sec and second_sec.status == StorageNode.STATUS_ONLINE:
258+
if primary_node.status == StorageNode.STATUS_ONLINE:
259+
try:
260+
storage_node_ops._failover_first_sec_ana(primary_node, second_sec)
261+
except Exception as ana_e:
262+
logger.error("Failover ANA for first sec %s failed: %s", node.get_id(), ana_e)
263+
242264
except Exception as e:
243265
logger.debug("Setting node to UNREACHABLE state failed")
244266
logger.error(e)

simplyblock_core/storage_node_ops.py

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
67144
def 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+
9131005
def 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

Comments
 (0)