Merge ~alexsander-souza/maas:lp1887558_fix_condense_luns into maas:master

Proposed by Alexsander de Souza
Status: Merged
Approved by: Alexsander de Souza
Approved revision: 3006b75a6b183c5ca9a96e179b733a32a4e71653
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~alexsander-souza/maas:lp1887558_fix_condense_luns
Merge into: maas:master
Diff against target: 251 lines (+182/-35)
2 files modified
src/metadataserver/builtin_scripts/hooks.py (+48/-35)
src/metadataserver/builtin_scripts/tests/test_hooks.py (+134/-0)
Reviewer Review Type Date Requested Status
Jack Lloyd-Walters Approve
MAAS Lander Approve
Review via email: mp+459043@code.launchpad.net

Commit message

improve multipath storage device support

include support to FiberChannel, JBOD, SCSI, iSCSI and HyperV multipath devices

fixes LP#1887558

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b lp1887558_fix_condense_luns lp:~alexsander-souza/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 3006b75a6b183c5ca9a96e179b733a32a4e71653

review: Approve
Revision history for this message
Jack Lloyd-Walters (lloydwaltersj) wrote :

As best I can see, this looks good!
The compiled regex at the start is very neat, good work!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/metadataserver/builtin_scripts/hooks.py b/src/metadataserver/builtin_scripts/hooks.py
2index 9904086..e34f760 100644
3--- a/src/metadataserver/builtin_scripts/hooks.py
4+++ b/src/metadataserver/builtin_scripts/hooks.py
5@@ -735,6 +735,32 @@ def _get_matching_block_device(block_devices, serial=None, id_path=None):
6 return None
7
8
9+_MP_PATH_ID = {
10+ "fc": [re.compile(r"^(?P<port>\w+)-(?P<lun>lun-(0x)?[\da-fA-F]+)$")],
11+ "vmbus": [re.compile(r"^(?P<guid>\w+)-(?P<lun>lun-(0x)?[\da-fA-F]+)$")],
12+ "sas": [
13+ re.compile(
14+ r"^(?P<sas_addr>0x[\da-fA-F]+)-(?P<lun>lun-(0x)?[\da-fA-F]+)$"
15+ ),
16+ re.compile(
17+ r"^exp0x[\da-fA-F]+-phy(?P<phy_id>(0x)?[\da-fA-F]+)-(?P<lun>lun-(0x)?[\da-fA-F]+)$"
18+ ),
19+ re.compile(
20+ r"^phy(?P<phy_id>(0x)?[\da-fA-F]+)-(?P<lun>lun-(0x)?[\da-fA-F]+)$"
21+ ),
22+ ],
23+ "ip": [
24+ re.compile(
25+ r"^[\.\-\w:]+-iscsi-(?P<target>[\.\-\w:]+)-(?P<lun>lun-(0x)?[\da-fA-F]+)$"
26+ )
27+ ],
28+}
29+
30+_DEV_PATH = re.compile(
31+ r"^(?P<bus>\w+)-(?P<bus_addr>[\da-fA-F:\.]+)-(?P<proto>\w+)-(?P<device>.*)$"
32+)
33+
34+
35 def _condense_luns(disks):
36 """Condense disks by LUN.
37
38@@ -747,46 +773,33 @@ def _condense_luns(disks):
39 serial_lun_map = defaultdict(list)
40 processed_disks = []
41 for disk in disks:
42- # split the device path from the form "key1-value1-key2-value2..." into
43- # a dict
44- tokens = disk.get("device_path", "").split("-")
45- device_path = dict(zip(tokens[::2], tokens[1::2]))
46- # LXD does not currently give a pci_address, it's included in the
47- # device_path. Add it if it isn't there. A USB disk include the
48- # USB device and PCI device the USB controller is connected to.
49- # Ignore USB devices as they are removable which MAAS doesn't
50- # model.
51- if (
52- "pci" in device_path
53- and "usb" not in device_path
54- and "pci_address" not in disk
55- ):
56- disk["pci_address"] = device_path["pci"]
57- if device_path.get("lun") not in ("0", None) and disk.get("serial"):
58- # multipath devices have LUN different from 0
59- serial_lun_map[(disk["serial"], device_path["lun"])].append(disk)
60+ dev_match = _DEV_PATH.match(disk.get("device_path", ""))
61+ if dev_match is None or dev_match["proto"] == "usb":
62+ processed_disks.append(disk)
63+ continue
64+
65+ proto = dev_match["proto"]
66+ device = dev_match["device"]
67+
68+ if dev_match["bus"] == "pci" and "pci_address" not in disk:
69+ disk["pci_address"] = dev_match["bus_addr"]
70+
71+ if disk["serial"] and (rexp_list := _MP_PATH_ID.get(proto)):
72+ for r in rexp_list:
73+ if m := r.match(device):
74+ serial_lun_map[(disk["serial"], m["lun"])].append(disk)
75+ break
76+ else:
77+ processed_disks.append(disk)
78 else:
79 processed_disks.append(disk)
80
81 for (serial, lun), paths in serial_lun_map.items():
82- # The first disk is usually the smallest id however doesn't usually
83- # have the device_id associated with it.
84- condensed_disk = paths[0]
85- if len(paths) > 1:
86- # Only tag a disk as multipath if it actually has multiple paths to
87- # it.
88+ mpaths = sorted(paths, key=itemgetter("id"))
89+ condensed_disk = mpaths[0]
90+ if len(mpaths) > 1:
91 condensed_disk["maas_multipath"] = True
92- for path in paths[1:]:
93- # Make sure the disk processed has the lowest id. Each path is
94- # given a name variant on a normal id. e.g sda, sdaa, sdab, sdb
95- # sdba, sdbb results in just sda and sdb for two multipath disks.
96- if path["id"] < condensed_disk["id"]:
97- condensed_disk["id"] = path["id"]
98- # The device differs per id, at least on IBM Z. MAAS doesn't
99- # use the device but keep it consistent anyway.
100- condensed_disk["device"] = path["device"]
101- # Only one path is given the device_id. Make sure the disk that
102- # is processed has it for the id_path.
103+ for path in mpaths[1:]:
104 if not condensed_disk.get("device_id") and path.get("device_id"):
105 condensed_disk["device_id"] = path["device_id"]
106 processed_disks.append(condensed_disk)
107diff --git a/src/metadataserver/builtin_scripts/tests/test_hooks.py b/src/metadataserver/builtin_scripts/tests/test_hooks.py
108index 9aa07e3..c37eacf 100644
109--- a/src/metadataserver/builtin_scripts/tests/test_hooks.py
110+++ b/src/metadataserver/builtin_scripts/tests/test_hooks.py
111@@ -3805,6 +3805,140 @@ class TestUpdateNodePhysicalBlockDevices(MAASServerTestCase):
112 },
113 )
114
115+ def test_condenses_luns_jbod(self):
116+ node = factory.make_Node()
117+ resources = deepcopy(SAMPLE_LXD_RESOURCES)
118+ expander1 = f"pci-0000:81:00.0-sas-exp0x{factory.make_hex_string(16)}"
119+ expander2 = f"pci-0000:81:00.0-sas-exp0x{factory.make_hex_string(16)}"
120+ lun1_model = factory.make_name("lun1_model")
121+ lun1_size = 1024**3 * random.randint(5, 100)
122+ lun1_block_size = random.choice([512, 1024, 4096])
123+ lun1_firmware_version = factory.make_name("lun1_firmware_version")
124+ lun1_serial = factory.make_name("lun1_serial")
125+ lun2_model = factory.make_name("lun2_model")
126+ lun2_size = 1024**3 * random.randint(5, 100)
127+ lun2_block_size = random.choice([512, 1024, 4096])
128+ lun2_firmware_version = factory.make_name("lun2_firmware_version")
129+ lun2_serial = factory.make_name("lun2_serial")
130+ resources["storage"]["disks"] = [
131+ {
132+ "id": "sda",
133+ "device": "8:0",
134+ "model": lun1_model,
135+ "type": "scsi",
136+ "read_only": False,
137+ "size": lun1_size,
138+ "removable": False,
139+ "numa_node": 0,
140+ "device_path": f"{expander1}-phy2-lun-0",
141+ "block_size": lun1_block_size,
142+ "firmware_version": lun1_firmware_version,
143+ "rpm": 0,
144+ "serial": lun1_serial,
145+ "device_id": "",
146+ "partitions": [],
147+ },
148+ {
149+ "id": "sdb",
150+ "device": "8:16",
151+ "model": lun2_model,
152+ "type": "scsi",
153+ "read_only": False,
154+ "size": lun2_size,
155+ "removable": False,
156+ "numa_node": 0,
157+ "device_path": f"{expander1}-phy5-lun-0",
158+ "block_size": lun2_block_size,
159+ "firmware_version": lun2_firmware_version,
160+ "rpm": 0,
161+ "serial": lun2_serial,
162+ "device_id": "",
163+ "partitions": [],
164+ },
165+ {
166+ "id": "sdc",
167+ "device": "8:112",
168+ "model": lun1_model,
169+ "type": "scsi",
170+ "read_only": False,
171+ "size": lun1_size,
172+ "removable": False,
173+ "numa_node": 0,
174+ "device_path": f"{expander2}-phy2-lun-0",
175+ "block_size": lun1_block_size,
176+ "firmware_version": lun1_firmware_version,
177+ "rpm": 0,
178+ "serial": lun1_serial,
179+ "device_id": "scsi-LUN1",
180+ "partitions": [],
181+ },
182+ {
183+ "id": "sdd",
184+ "device": "8:118",
185+ "model": lun2_model,
186+ "type": "scsi",
187+ "read_only": False,
188+ "size": lun2_size,
189+ "removable": False,
190+ "numa_node": 0,
191+ "device_path": f"{expander2}-phy5-lun-0",
192+ "block_size": lun2_block_size,
193+ "firmware_version": lun2_firmware_version,
194+ "rpm": 0,
195+ "serial": lun2_serial,
196+ "device_id": "scsi-LUN2",
197+ "partitions": [],
198+ },
199+ ]
200+
201+ _update_node_physical_block_devices(
202+ node, resources, create_numa_nodes(node)
203+ )
204+
205+ self.assertEqual(2, node.physicalblockdevice_set.count())
206+ sda = node.physicalblockdevice_set.get(name="sda")
207+ self.assertEqual(
208+ {
209+ "model": lun1_model,
210+ "serial": lun1_serial,
211+ "id_path": "/dev/disk/by-id/scsi-LUN1",
212+ "size": lun1_size,
213+ "block_size": lun1_block_size,
214+ "firmware_version": lun1_firmware_version,
215+ "tags": ["multipath"],
216+ },
217+ {
218+ "model": sda.model,
219+ "serial": sda.serial,
220+ "id_path": sda.id_path,
221+ "size": sda.size,
222+ "block_size": sda.block_size,
223+ "firmware_version": sda.firmware_version,
224+ "tags": sda.tags,
225+ },
226+ )
227+ sdb = node.physicalblockdevice_set.get(name="sdb")
228+ self.assertEqual(
229+ {
230+ "model": lun2_model,
231+ "serial": lun2_serial,
232+ "id_path": "/dev/disk/by-id/scsi-LUN2",
233+ "size": lun2_size,
234+ "block_size": lun2_block_size,
235+ "firmware_version": lun2_firmware_version,
236+ "tags": ["multipath"],
237+ },
238+ {
239+ "model": sdb.model,
240+ "serial": sdb.serial,
241+ "id_path": sdb.id_path,
242+ "size": sdb.size,
243+ "block_size": sdb.block_size,
244+ "firmware_version": sdb.firmware_version,
245+ "tags": sdb.tags,
246+ },
247+ )
248+
249 def test_no_condense_luns_different_serial(self):
250 node = factory.make_Node()
251 resources = deepcopy(SAMPLE_LXD_RESOURCES)

Subscribers

People subscribed via source and target branches