Merge ~mwhudson/curtin:v2-disk-identification into curtin:master

Proposed by Michael Hudson-Doyle
Status: Merged
Approved by: Michael Hudson-Doyle
Approved revision: 8aae411a5ffe9057bf3c5fc9d02f1237bf3cf14a
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~mwhudson/curtin:v2-disk-identification
Merge into: curtin:master
Diff against target: 553 lines (+384/-44)
7 files modified
curtin/block/multipath.py (+4/-3)
curtin/commands/block_meta.py (+130/-39)
curtin/udev.py (+9/-0)
examples/tests/multipath-reuse.yaml (+1/-1)
examples/tests/multipath.yaml (+1/-1)
requirements.txt (+1/-0)
tests/unittests/test_commands_block_meta.py (+238/-0)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Dan Bungert Approve
Review via email: mp+415700@code.launchpad.net

Commit message

block_meta: all fields on a disk action must match with v2 config

This is for https://bugs.launchpad.net/subiquity/+bug/1929213 and
similar reports.

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
886eb19... by Michael Hudson-Doyle

add pyudev to requirements

4642442... by Michael Hudson-Doyle

oops

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Dan Bungert (dbungert) wrote :

The log comment is the only reason I'm marking this as needs fixing.

review: Needs Fixing
a2ac4e3... by Michael Hudson-Doyle

respond to review

Revision history for this message
Michael Hudson-Doyle (mwhudson) :
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
7f839a7... by Michael Hudson-Doyle

have v2 get_path_to_storage_volume promote multipath members to multipath device more generally

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
8aae411... by Michael Hudson-Doyle

update an example

Revision history for this message
Dan Bungert (dbungert) wrote :

Looks like this was fixed a while ago.

review: Approve
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

The reason I haven't merged this is that it broke some vmtests. Now that jenkins works again (?) I can iterate on it again I guess.

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

On reflection (and trying to help another user who has run into this problem) I think we should merge this and deal with any fallout as it happens. Generally speaking the new behaviour is better.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Dan Bungert (dbungert) wrote :

+1 to merge in spite of known vmtest issues.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/curtin/block/multipath.py b/curtin/block/multipath.py
2index 0f9170e..8b1267a 100644
3--- a/curtin/block/multipath.py
4+++ b/curtin/block/multipath.py
5@@ -186,13 +186,14 @@ def find_mpath_partitions(mpath_id):
6 if mp_id.startswith(mpath_id + '-'))
7
8
9-def get_mpath_id_from_device(device):
10+def get_mpath_id_from_device(device, info=None):
11 # /dev/dm-X
12- if is_mpath_device(device) or is_mpath_partition(device):
13+ if info is None:
14 info = udev.udevadm_info(device)
15+ if is_mpath_device(device, info) or is_mpath_partition(device, info):
16 return info.get('DM_NAME')
17 # /dev/sdX
18- if is_mpath_member(device):
19+ if is_mpath_member(device, info):
20 return find_mpath_id_by_path(device)
21
22 return None
23diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
24index cef0e49..74c491c 100644
25--- a/curtin/commands/block_meta.py
26+++ b/curtin/commands/block_meta.py
27@@ -13,8 +13,13 @@ from curtin.storage_config import (extract_storage_ordered_dict,
28
29
30 from . import populate_one_subcmd
31-from curtin.udev import (compose_udev_equality, udevadm_settle,
32- udevadm_trigger, udevadm_info)
33+from curtin.udev import (
34+ compose_udev_equality,
35+ udev_all_block_device_properties,
36+ udevadm_info,
37+ udevadm_settle,
38+ udevadm_trigger,
39+ )
40
41 import glob
42 import json
43@@ -25,6 +30,7 @@ import sys
44 import tempfile
45 import time
46
47+
48 FstabData = namedtuple(
49 "FstabData", ('spec', 'path', 'fstype', 'options', 'freq', 'passno',
50 'device'))
51@@ -429,6 +435,122 @@ def get_poolname(info, storage_config):
52 return poolname
53
54
55+def v1_get_path_to_disk(vol):
56+ volume_path = None
57+ for disk_key in ['wwn', 'serial', 'device_id', 'path']:
58+ vol_value = vol.get(disk_key)
59+ try:
60+ if not vol_value:
61+ continue
62+ if disk_key in ['wwn', 'serial']:
63+ volume_path = block.lookup_disk(vol_value)
64+ elif disk_key == 'path':
65+ if vol_value.startswith('iscsi:'):
66+ i = iscsi.ensure_disk_connected(vol_value)
67+ volume_path = os.path.realpath(i.devdisk_path)
68+ else:
69+ # resolve any symlinks to the dev_kname so
70+ # sys/class/block access is valid. ie, there are no
71+ # udev generated values in sysfs
72+ volume_path = os.path.realpath(vol_value)
73+ # convert /dev/sdX to /dev/mapper/mpathX value
74+ if multipath.is_mpath_member(volume_path):
75+ volume_path = '/dev/mapper/' + (
76+ multipath.get_mpath_id_from_device(volume_path))
77+ elif disk_key == 'device_id':
78+ dasd_device = dasd.DasdDevice(vol_value)
79+ volume_path = dasd_device.devname
80+ except ValueError:
81+ continue
82+ # verify path exists otherwise try the next key
83+ if os.path.exists(volume_path):
84+ break
85+ else:
86+ volume_path = None
87+
88+ if volume_path is None:
89+ raise ValueError("Failed to find storage volume id='%s' config: %s"
90+ % (vol['id'], vol))
91+
92+ return volume_path
93+
94+
95+def v2_get_path_to_disk(vol):
96+ all_disks = []
97+ link2dev = {}
98+
99+ for dev in udev_all_block_device_properties():
100+ if 'DM_PART' in dev or 'PARTN' in dev:
101+ continue
102+ for link in dev.get('DEVLINKS', '').split():
103+ link2dev[link] = dev
104+ link2dev[dev['DEVNAME']] = dev
105+ all_disks.append(dev)
106+
107+ def disks_matching(key, value):
108+ return [dev for dev in all_disks if key in dev and dev[key] == value]
109+
110+ def disk_by_path(path):
111+ return link2dev[path]
112+
113+ def disk_by_keys(val, *keys):
114+ for key in keys:
115+ devs = disks_matching(key, val)
116+ if devs:
117+ return devs
118+ return []
119+
120+ cands = []
121+
122+ def add_cands(*devs):
123+ new_devs = []
124+ for dev in devs:
125+ if multipath.is_mpath_member(dev['DEVNAME'], dev):
126+ mpath_id = multipath.get_mpath_id_from_device(
127+ dev['DEVNAME'], dev)
128+ dev = link2dev['/dev/mapper/' + mpath_id]
129+ new_devs.append(dev)
130+ cands.append(set([dev['DEVNAME'] for dev in new_devs]))
131+
132+ if 'wwn' in vol:
133+ add_cands(*disk_by_keys(vol['wwn'], 'DM_WWN', 'ID_WWN'))
134+ if 'serial' in vol:
135+ add_cands(
136+ *disk_by_keys(
137+ vol['serial'], 'DM_SERIAL', 'ID_SERIAL', 'ID_SERIAL_SHORT'))
138+ if 'device_id' in vol:
139+ dasd_device = dasd.DasdDevice(vol['device_id'])
140+ cands.append(set([dasd_device.devname]))
141+ volume_path = dasd_device.devname
142+ # verify path exists otherwise try the next key
143+ if os.path.exists(volume_path):
144+ cands.append(set([volume_path]))
145+ if 'path' in vol:
146+ path = vol['path']
147+ if path.startswith('iscsi:'):
148+ i = iscsi.ensure_disk_connected(path)
149+ path = i.devdisk_path
150+ dev = disk_by_path(path)
151+ if dev is not None:
152+ add_cands(dev)
153+ else:
154+ cands.append(set())
155+
156+ LOG.debug('found candidate disks %s', cands)
157+
158+ cands = set.intersection(*cands)
159+
160+ if len(cands) == 0:
161+ raise ValueError("Failed to find storage volume id='%s' config: %s"
162+ % (vol['id'], vol))
163+ if len(cands) > 1:
164+ raise ValueError(
165+ "Storage volume id='%s' config: %s identified multiple devices"
166+ % (vol['id'], vol))
167+
168+ return cands.pop()
169+
170+
171 def get_path_to_storage_volume(volume, storage_config):
172 # Get path to block device for volume. Volume param should refer to id of
173 # volume in storage config
174@@ -455,41 +577,10 @@ def get_path_to_storage_volume(volume, storage_config):
175 elif vol.get('type') == "disk":
176 # Get path to block device for disk. Device_id param should refer
177 # to id of device in storage config
178- volume_path = None
179- for disk_key in ['wwn', 'serial', 'device_id', 'path']:
180- vol_value = vol.get(disk_key)
181- try:
182- if not vol_value:
183- continue
184- if disk_key in ['wwn', 'serial']:
185- volume_path = block.lookup_disk(vol_value)
186- elif disk_key == 'path':
187- if vol_value.startswith('iscsi:'):
188- i = iscsi.ensure_disk_connected(vol_value)
189- volume_path = os.path.realpath(i.devdisk_path)
190- else:
191- # resolve any symlinks to the dev_kname so
192- # sys/class/block access is valid. ie, there are no
193- # udev generated values in sysfs
194- volume_path = os.path.realpath(vol_value)
195- # convert /dev/sdX to /dev/mapper/mpathX value
196- if multipath.is_mpath_member(volume_path):
197- volume_path = '/dev/mapper/' + (
198- multipath.get_mpath_id_from_device(volume_path))
199- elif disk_key == 'device_id':
200- dasd_device = dasd.DasdDevice(vol_value)
201- volume_path = dasd_device.devname
202- except ValueError:
203- continue
204- # verify path exists otherwise try the next key
205- if os.path.exists(volume_path):
206- break
207- else:
208- volume_path = None
209-
210- if volume_path is None:
211- raise ValueError("Failed to find storage volume id='%s' config: %s"
212- % (vol['id'], vol))
213+ if getattr(storage_config, 'version', 1) > 1:
214+ volume_path = v2_get_path_to_disk(vol)
215+ else:
216+ volume_path = v1_get_path_to_disk(vol)
217
218 elif vol.get('type') == "lvm_partition":
219 # For lvm partitions, a directory in /dev/ should be present with the
220@@ -2067,8 +2158,8 @@ def meta_custom(args):
221
222 storage_config_dict = extract_storage_ordered_dict(cfg)
223
224- version = cfg['storage']['version']
225- if version > 1:
226+ storage_config_dict.version = cfg['storage']['version']
227+ if storage_config_dict.version > 1:
228 from curtin.commands.block_meta_v2 import (
229 disk_handler_v2,
230 partition_handler_v2,
231diff --git a/curtin/udev.py b/curtin/udev.py
232index 2b7a46e..fa4f940 100644
233--- a/curtin/udev.py
234+++ b/curtin/udev.py
235@@ -129,4 +129,13 @@ def udevadm_info(path=None):
236 return info
237
238
239+def udev_all_block_device_properties():
240+ import pyudev
241+ props = []
242+ c = pyudev.Context()
243+ for device in c.list_devices(subsystem='block'):
244+ props.append(dict(device.properties))
245+ return props
246+
247+
248 # vi: ts=4 expandtab syntax=python
249diff --git a/examples/tests/multipath-reuse.yaml b/examples/tests/multipath-reuse.yaml
250index f008848..2f2b5b1 100644
251--- a/examples/tests/multipath-reuse.yaml
252+++ b/examples/tests/multipath-reuse.yaml
253@@ -23,7 +23,7 @@ storage:
254 - id: sda
255 type: disk
256 ptable: msdos
257- serial: 'IPR-0 1234567890'
258+ serial: 'IPR-0_1234567890'
259 name: mpath_a
260 grub_device: true
261 multipath: mpatha
262diff --git a/examples/tests/multipath.yaml b/examples/tests/multipath.yaml
263index a3b536f..d0bd358 100644
264--- a/examples/tests/multipath.yaml
265+++ b/examples/tests/multipath.yaml
266@@ -7,7 +7,7 @@ storage:
267 - id: sda
268 type: disk
269 ptable: msdos
270- serial: 'IPR-0 1234567890'
271+ serial: 'IPR-0_1234567890'
272 name: mpath_a
273 wipe: superblock
274 grub_device: true
275diff --git a/requirements.txt b/requirements.txt
276index 6afa3b8..d8d1a3d 100644
277--- a/requirements.txt
278+++ b/requirements.txt
279@@ -1,3 +1,4 @@
280+pyudev
281 pyyaml
282 oauthlib
283 # For validation of storate configuration format
284diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
285index bd0ddf6..7067bc0 100644
286--- a/tests/unittests/test_commands_block_meta.py
287+++ b/tests/unittests/test_commands_block_meta.py
288@@ -35,6 +35,11 @@ class TestGetPathToStorageVolume(CiTestCase):
289 self.add_patch(basepath + 'devsync', 'm_devsync')
290 self.add_patch(basepath + 'util.subp', 'm_subp')
291 self.add_patch(basepath + 'multipath.is_mpath_member', 'm_mp')
292+ self.m_mp.return_value = False
293+ self.add_patch(
294+ basepath + 'multipath.get_mpath_id_from_device', 'm_get_mpath_id')
295+ self.add_patch(
296+ basepath + 'udev_all_block_device_properties', 'm_udev_all')
297
298 def test_block_lookup_called_with_disk_wwn(self):
299 volume = 'mydisk'
300@@ -69,6 +74,7 @@ class TestGetPathToStorageVolume(CiTestCase):
301 self.assertEqual(expected_calls, self.m_lookup.call_args_list)
302
303 def test_fallback_to_path_when_lookup_wwn_serial_fail(self):
304+ self.m_mp.return_value = False
305 volume = 'mydisk'
306 wwn = self.random_string()
307 serial = self.random_string()
308@@ -87,6 +93,7 @@ class TestGetPathToStorageVolume(CiTestCase):
309 self.assertEqual(path, result)
310
311 def test_block_lookup_not_called_with_wwn_or_serial_keys(self):
312+ self.m_mp.return_value = False
313 volume = 'mydisk'
314 path = "/%s/%s" % (self.random_string(), self.random_string())
315 cfg = {'id': volume, 'type': 'disk', 'path': path}
316@@ -118,6 +125,237 @@ class TestGetPathToStorageVolume(CiTestCase):
317 self.assertEqual(expected_calls, self.m_lookup.call_args_list)
318 self.m_exists.assert_has_calls([call(path)])
319
320+ def test_v2_match_serial(self):
321+ serial = self.random_string()
322+ devname = self.random_string()
323+ self.m_udev_all.return_value = [
324+ {'DEVNAME': devname, 'ID_SERIAL': serial},
325+ ]
326+ disk_id = self.random_string()
327+ cfg = {'id': disk_id, 'type': 'disk', 'serial': serial}
328+ s_cfg = OrderedDict({disk_id: cfg})
329+ s_cfg.version = 2
330+ self.assertEqual(
331+ devname,
332+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
333+
334+ def test_v2_match_serial_short(self):
335+ serial = self.random_string()
336+ serial_short = self.random_string()
337+ devname = self.random_string()
338+ self.m_udev_all.return_value = [{
339+ 'DEVNAME': devname,
340+ 'ID_SERIAL': serial,
341+ 'ID_SERIAL_SHORT': serial_short,
342+ }]
343+ disk_id = self.random_string()
344+ cfg = {'id': disk_id, 'type': 'disk', 'serial': serial_short}
345+ s_cfg = OrderedDict({disk_id: cfg})
346+ s_cfg.version = 2
347+ self.assertEqual(
348+ devname,
349+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
350+
351+ def test_v2_match_serial_skips_partition(self):
352+ serial = self.random_string()
353+ devname = self.random_string()
354+ self.m_udev_all.return_value = [
355+ {'DEVNAME': devname, 'ID_SERIAL': serial},
356+ {'DEVNAME': devname+'p1', 'ID_SERIAL': serial, 'PARTN': "1"},
357+ ]
358+ disk_id = self.random_string()
359+ cfg = {'id': disk_id, 'type': 'disk', 'serial': serial}
360+ s_cfg = OrderedDict({disk_id: cfg})
361+ s_cfg.version = 2
362+ self.assertEqual(
363+ devname,
364+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
365+
366+ def test_v2_match_serial_prefer_mpath(self):
367+ serial = self.random_string()
368+ devname1 = self.random_string()
369+ devname2 = self.random_string()
370+ devname_dm = self.random_string()
371+ self.m_udev_all.return_value = [
372+ {'DEVNAME': devname1, 'ID_SERIAL': serial},
373+ {'DEVNAME': devname2, 'ID_SERIAL': serial},
374+ {'DEVNAME': devname_dm, 'DM_SERIAL': serial},
375+ ]
376+ disk_id = self.random_string()
377+ cfg = {'id': disk_id, 'type': 'disk', 'serial': serial}
378+ s_cfg = OrderedDict({disk_id: cfg})
379+ s_cfg.version = 2
380+ self.assertEqual(
381+ devname_dm,
382+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
383+
384+ def test_v2_match_serial_mpath_skip_partition(self):
385+ serial = self.random_string()
386+ devname1 = self.random_string()
387+ devname2 = self.random_string()
388+ devname_dm = self.random_string()
389+ self.m_udev_all.return_value = [
390+ {'DEVNAME': devname1, 'ID_SERIAL': serial},
391+ {'DEVNAME': devname2, 'ID_SERIAL': serial},
392+ {'DEVNAME': devname_dm, 'DM_SERIAL': serial},
393+ {'DEVNAME': devname_dm+'p1', 'DM_SERIAL': serial, 'DM_PART': '1'},
394+ ]
395+ disk_id = self.random_string()
396+ cfg = {'id': disk_id, 'type': 'disk', 'serial': serial}
397+ s_cfg = OrderedDict({disk_id: cfg})
398+ s_cfg.version = 2
399+ self.assertEqual(
400+ devname_dm,
401+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
402+
403+ def test_v2_match_wwn(self):
404+ wwn = self.random_string()
405+ devname = self.random_string()
406+ self.m_udev_all.return_value = [
407+ {'DEVNAME': devname, 'ID_WWN': wwn},
408+ ]
409+ disk_id = self.random_string()
410+ cfg = {'id': disk_id, 'type': 'disk', 'wwn': wwn}
411+ s_cfg = OrderedDict({disk_id: cfg})
412+ s_cfg.version = 2
413+ self.assertEqual(
414+ devname,
415+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
416+
417+ def test_v2_match_wwn_prefer_mpath(self):
418+ wwn = self.random_string()
419+ devname1 = self.random_string()
420+ devname2 = self.random_string()
421+ devname_dm = self.random_string()
422+ self.m_udev_all.return_value = [
423+ {'DEVNAME': devname1, 'ID_WWN': wwn},
424+ {'DEVNAME': devname2, 'ID_WWN': wwn},
425+ {'DEVNAME': devname_dm, 'DM_WWN': wwn},
426+ ]
427+ disk_id = self.random_string()
428+ cfg = {'id': disk_id, 'type': 'disk', 'wwn': wwn}
429+ s_cfg = OrderedDict({disk_id: cfg})
430+ s_cfg.version = 2
431+ self.assertEqual(
432+ devname_dm,
433+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
434+
435+ def test_v2_match_serial_and_wwn(self):
436+ serial = self.random_string()
437+ wwn = self.random_string()
438+ devname = self.random_string()
439+ self.m_udev_all.return_value = [
440+ {'DEVNAME': devname, 'ID_SERIAL': serial, 'ID_WWN': wwn},
441+ ]
442+ disk_id = self.random_string()
443+ cfg = {'id': disk_id, 'type': 'disk', 'serial': serial, 'wwn': wwn}
444+ s_cfg = OrderedDict({disk_id: cfg})
445+ s_cfg.version = 2
446+ self.assertEqual(
447+ devname,
448+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
449+
450+ def test_v2_different_serial_same_wwn(self):
451+ serial1 = self.random_string()
452+ serial2 = self.random_string()
453+ wwn = self.random_string()
454+ devname1 = self.random_string()
455+ devname2 = self.random_string()
456+ self.m_udev_all.return_value = [
457+ {'DEVNAME': devname1, 'ID_SERIAL': serial1, 'ID_WWN': wwn},
458+ {'DEVNAME': devname2, 'ID_SERIAL': serial2, 'ID_WWN': wwn},
459+ ]
460+ disk_id = self.random_string()
461+ cfg = {'id': disk_id, 'type': 'disk', 'serial': serial2, 'wwn': wwn}
462+ s_cfg = OrderedDict({disk_id: cfg})
463+ s_cfg.version = 2
464+ self.assertEqual(
465+ devname2,
466+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
467+
468+ def test_v2_fails_duplicate_wwn(self):
469+ serial1 = self.random_string()
470+ serial2 = self.random_string()
471+ wwn = self.random_string()
472+ devname1 = self.random_string()
473+ devname2 = self.random_string()
474+ self.m_udev_all.return_value = [
475+ {'DEVNAME': devname1, 'ID_SERIAL': serial1, 'ID_WWN': wwn},
476+ {'DEVNAME': devname2, 'ID_SERIAL': serial2, 'ID_WWN': wwn},
477+ ]
478+ disk_id = self.random_string()
479+ cfg = {'id': disk_id, 'type': 'disk', 'wwn': wwn}
480+ s_cfg = OrderedDict({disk_id: cfg})
481+ s_cfg.version = 2
482+ with self.assertRaises(ValueError):
483+ block_meta.get_path_to_storage_volume(disk_id, s_cfg)
484+
485+ def test_v2_match_path(self):
486+ self.m_mp.return_value = False
487+ devname = self.random_string()
488+ self.m_udev_all.return_value = [
489+ {'DEVNAME': devname},
490+ ]
491+ disk_id = self.random_string()
492+ cfg = {'id': disk_id, 'type': 'disk', 'path': devname}
493+ s_cfg = OrderedDict({disk_id: cfg})
494+ s_cfg.version = 2
495+ self.assertEqual(
496+ devname,
497+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
498+
499+ def test_v2_match_path_link(self):
500+ self.m_mp.return_value = False
501+ devname = self.random_string()
502+ link1 = self.random_string()
503+ link2 = self.random_string()
504+ links = link1 + ' ' + link2
505+ self.m_udev_all.return_value = [
506+ {'DEVNAME': devname, 'DEVLINKS': links},
507+ ]
508+ disk_id = self.random_string()
509+ cfg = {'id': disk_id, 'type': 'disk', 'path': link1}
510+ s_cfg = OrderedDict({disk_id: cfg})
511+ s_cfg.version = 2
512+ self.assertEqual(
513+ devname,
514+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
515+
516+ def test_v2_match_path_prefers_multipath(self):
517+ self.m_mp.return_value = True
518+ self.m_get_mpath_id.return_value = 'mpatha'
519+ devname1 = self.random_string()
520+ devname2 = self.random_string()
521+ self.m_udev_all.return_value = [
522+ {'DEVNAME': devname1, 'DEVLINKS': ''},
523+ {'DEVNAME': devname2, 'DEVLINKS': '/dev/mapper/mpatha'},
524+ ]
525+ disk_id = self.random_string()
526+ cfg = {'id': disk_id, 'type': 'disk', 'path': devname1}
527+ s_cfg = OrderedDict({disk_id: cfg})
528+ s_cfg.version = 2
529+ self.assertEqual(
530+ devname2,
531+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
532+
533+ def test_v2_match_serial_prefers_multipath(self):
534+ self.m_mp.return_value = True
535+ self.m_get_mpath_id.return_value = 'mpatha'
536+ devname1 = self.random_string()
537+ devname2 = self.random_string()
538+ serial = self.random_string()
539+ self.m_udev_all.return_value = [
540+ {'DEVNAME': devname1, 'DEVLINKS': '', 'ID_SERIAL': serial},
541+ {'DEVNAME': devname2, 'DEVLINKS': '/dev/mapper/mpatha'},
542+ ]
543+ disk_id = self.random_string()
544+ cfg = {'id': disk_id, 'type': 'disk', 'serial': serial}
545+ s_cfg = OrderedDict({disk_id: cfg})
546+ s_cfg.version = 2
547+ self.assertEqual(
548+ devname2,
549+ block_meta.get_path_to_storage_volume(disk_id, s_cfg))
550+
551
552 class TestBlockMetaSimple(CiTestCase):
553 def setUp(self):

Subscribers

People subscribed via source and target branches