Merge lp:~raharper/curtin/trunk.vmtest-multipath into lp:~curtin-dev/curtin/trunk

Proposed by Ryan Harper
Status: Merged
Merged at revision: 396
Proposed branch: lp:~raharper/curtin/trunk.vmtest-multipath
Merge into: lp:~curtin-dev/curtin/trunk
Diff against target: 846 lines (+513/-68)
10 files modified
curtin/block/__init__.py (+9/-3)
curtin/commands/block_meta.py (+7/-2)
curtin/commands/curthooks.py (+17/-1)
curtin/util.py (+56/-0)
examples/tests/basic_scsi.yaml (+72/-0)
examples/tests/multipath.yaml (+38/-0)
tests/vmtests/__init__.py (+97/-45)
tests/vmtests/test_basic.py (+117/-0)
tests/vmtests/test_multipath.py (+63/-0)
tools/launch (+37/-17)
To merge this branch: bzr merge lp:~raharper/curtin/trunk.vmtest-multipath
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Scott Moser (community) Approve
Christian Ehrhardt  Needs Fixing
Review via email: mp+297007@code.launchpad.net

Commit message

Add multipath testing

Add multipath testing via a test-case option, cls.multipath which will
if set to true, duplicate all disks in a test class. This has the effect
of looking exactly like multipath setups where we see the same device
with two paths.

To handle devices with wwn or serial numbers with spaces in them
curtin.block was updated to ensure it supports finding devices via there
serial or wwn attribute. Specifically for serial numbers with spaces, we
currently will escape the spaces to underscores; this is supported by
observing the names that show up in /dev/disk/by-id/* for those devices.

For wwn, /dev/disk/by-id/wwn-$wwn ; curtin resolves the symlink target to
obtain a kernel devname for use with sysfs.

For serial with spaces, curtin attempts to remove whitespace like udev to
find the disk under /dev/disk/by-id/ and determine a devname.

Xenial multipath fails without ensuring the multipath/bindings file is
written with whitespace removed; this is triggered on in-target mulitpath
versions >= 1.5.0 fixing LP:1551937.

Description of the change

Add multipath testing

Add multipath testing via a test-case option, cls.multipath which will
if set to true, duplicate all disks in a test class. This has the effect
of looking exactly like multipath setups where we see the same device
with two paths.

To handle devices with wwn or serial numbers with spaces in them
curtin.block was updated to ensure it supports finding devices via there
serial or wwn attribute. Specifically for serial numbers with spaces, we
currently will escape the spaces to underscores; this is supported by
observing the names that show up in /dev/disk/by-id/* for those devices.

For wwn, /dev/disk/by-id/wwn-$wwn ; curtin resolves the symlink target to
obtain a kernel devname for use with sysfs.

For serial with spaces, curtin attempts to remove whitespace like udev to
find the disk under /dev/disk/by-id/ and determine a devname.

Xenial multipath fails without ensuring the multipath/bindings file is
written with whitespace removed; this is triggered on in-target mulitpath
versions >= 1.5.0 fixing LP:1551937.

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

Hi Ryan,
nice work on multipath testing!

I have found nothing severe but a collection of things that could be more clear, more polished or just were unclear at least to me.

review: Needs Fixing
Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

Probably also worth to mention that actually running the tests failed me.
I did an sync-images to ensure I'm not just on some older sh$§.

But still running the full testsuite on your branch won't fully work:
Note at the same environment my last branch works (just to ensure the system setup isn't borked)

rm -rf output/; CURTIN_VMTEST_KEEP_DATA_FAIL=all ./tools/jenkins-runner -vv --nologcapture --processes=5 --process-timeout=3600 tests/vmtests 2>&1 | tee ~/curtin-test-multipath.log; rm -rf output/
Yours: => http://paste.ubuntu.com/17167038/
Mine as reference: => http://paste.ubuntu.com/17167042/

You can give it a look yourself it is on ubuntu@horsea:/mnt/nvme/trunk.vmtest-multipath and the logfiles of my runs are in home.
I imported your LP id to ubuntu@

Revision history for this message
Scott Moser (smoser) wrote :

On Fri, 10 Jun 2016, ChristianEhrhardt wrote:

> Review: Needs Fixing
>
> Hi Ryan,
> nice work on multipath testing!
>
> I have found nothing severe but a collection of things that could be more clear, more polished or just were unclear at least to me.
>
> Do you think it would make sense to make this a parameter maybe from cls.multipath.num_parallel or so?
> Effectively what you did is already so great, I don't see why it has to be always 2.
> That way you could do some sort of limit testing by setting it to (48*2)*(96*2) which would be the system z max.
> Not really that number :-), but driving the test with 2,16,64 could be a great check!
>
> General question: Is there a limit on qemu (commandline) that we have to be scared of?

Probably not too much to worry about. I'm pretty sure that its total of
command line + environment < 128k.

I just tried:
  $ ./foo.sh
  128000
  129000
  130000
  131000
  /tmp/foo.sh: 8: /tmp/foo.sh: /bin/true: Argument list too long
  i=131072 2
  $ cat foo.sh
  #!/bin/sh
  b=""; i=0
  while i=$(($i+1)) && b="$b."; do
      [ $i -lt 128000 ] && continue
      [ $(($i%1000)) -eq 0 ] && echo $i
      /bin/true "$b" || { ret=$?; break; }
  done
  echo i=$i $ret

Revision history for this message
Ryan Harper (raharper) wrote :
Download full text (16.9 KiB)

> Hi Ryan,
> nice work on multipath testing!
>
> I have found nothing severe but a collection of things that could be more clear, more polished or just were unclear at least to me.
>

Thanks for the review Christian. Picking up most of your suggestions.
A few things will need to put off for now.

> > === modified file 'curtin/block/__init__.py'
> > --- curtin/block/__init__.py 2016-04-22 16:24:00 +0000
> > +++ curtin/block/__init__.py 2016-06-09 21:47:02 +0000
> > @@ -356,6 +356,7 @@
> > cmd.append('--replace-whitespace')
> > try:
> > (out, err) = util.subp(cmd, capture=True)
> > + LOG.debug("scsi_id output raw:\n%s", out)
>
> Since it is debug level anyway I'd think it would be nice to report the "err"
> as well. I mean there could be err output even without
> util.ProcessExecutionError in some cases - and especially those are the ones we
> will read the debug level info.
>

OK

> > scsi_wwid = out.rstrip('\n')
> > return scsi_wwid
> > except util.ProcessExecutionError as e:
> >
> > === modified file 'curtin/commands/block_meta.py'
> > --- curtin/commands/block_meta.py 2016-04-22 16:24:00 +0000
> > +++ curtin/commands/block_meta.py 2016-06-09 21:47:02 +0000
> > @@ -379,9 +379,14 @@
> > if vol.get('serial'):
> > volume_path = block.lookup_disk(vol.get('serial'))
> > elif vol.get('path'):
> > - volume_path = vol.get('path')
> > + # resolve any symlinks to the dev_kname so sys/class/block access
> > + # is valid. ie, there are no udev generated values in sysfs
> > + volume_path = os.path.realpath(vol.get('path'))
>
> I'm not sure so this is honestly just a question - could there be cases
> where os.path.realpath would throw some kind of OSError? Like on broken
> links or anything - and if so should we guard us against it?
> (same 4 lines below)

I'm sure os.path.realpath() throw and exception, however, I don't think there
is anything that we can do about that and still recover. That is, if
we fail to find the realpath to the device, we cannot fix that, and we'd exit.

Without catching the exception, we get the same semantics by allowing the
error to propigate upward.

> > === modified file 'curtin/commands/curthooks.py'
> > --- curtin/commands/curthooks.py 2016-05-17 21:14:36 +0000
> > +++ curtin/commands/curthooks.py 2016-06-09 21:47:02 +0000
> > @@ -638,6 +638,20 @@
> > LOG.info("Detected multipath devices. Installing support via %s", mppkgs)
> >
> > util.install_packages(mppkgs, target=target)
> > + replace_spaces = True
> > + try:
> > + # check in-target version
> > + pkg_ver = util.get_package_version('multipath-tools', target)
> > + upstream = pkg_ver.split('-')[0]
> > + major, minor, micro = upstream.split(".", 2)
> > + val = 1000 * int(major) + 100 * int(minor)
>
> Maybe moving the splitting into util as well (or an extra .py).
> This is very format depending, but I'd think there are only three or four kinds in Ubunutu.
> It would be nice for re-usability if you would define that as util for this format.
> Whoever needs another ...

393. By Ryan Harper

Only use the filename of the output disk, not the whole path.

394. By Ryan Harper

Add error output along side scsi_id debug output.

395. By Ryan Harper

Not all vmtests use storage config, fail gracefully

396. By Ryan Harper

Move version parsing into utility method, return version dictionary for callers

397. By Ryan Harper

Use disk_wwns to match disk_serials

398. By Ryan Harper

Replace hardcoded number of paths for multipath to class variable, cls.multipath_num_paths; default to 2

399. By Ryan Harper

Replace single character temp variable in disk parameter construction; update formatting.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

I approve of this branch.
We actually deployed a power8 system with multipath for xenial with it at revno 399.
I had conversation with Ryan in irc, and asked that we improve the get_package_version to do the multiplication of major/minor/micro centrally. something like this: http://paste.ubuntu.com/17806224/

i'd like that fixed, and then please merge. whoowho!

review: Approve
400. By Ryan Harper

merge from trunk

401. By Ryan Harper

Update get_package_version to move sematic_version calculation internally.

402. By Ryan Harper

fixes from diglett run

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'curtin/block/__init__.py'
2--- curtin/block/__init__.py 2016-04-22 16:24:00 +0000
3+++ curtin/block/__init__.py 2016-06-24 19:28:31 +0000
4@@ -356,6 +356,7 @@
5 cmd.append('--replace-whitespace')
6 try:
7 (out, err) = util.subp(cmd, capture=True)
8+ LOG.debug("scsi_id output raw:\n%s\nerror:\n%s", out, err)
9 scsi_wwid = out.rstrip('\n')
10 return scsi_wwid
11 except util.ProcessExecutionError as e:
12@@ -474,9 +475,14 @@
13 """
14 # Get all volumes in /dev/disk/by-id/ containing the serial string. The
15 # string specified can be either in the short or long serial format
16- disks = list(filter(lambda x: serial in x, os.listdir("/dev/disk/by-id/")))
17+ # hack, some serials have spaces, udev usually converts ' ' -> '_'
18+ serial_udev = serial.replace(' ', '_')
19+ LOG.info('Processing serial %s via udev to %s', serial, serial_udev)
20+
21+ disks = list(filter(lambda x: serial_udev in x,
22+ os.listdir("/dev/disk/by-id/")))
23 if not disks or len(disks) < 1:
24- raise ValueError("no disk with serial '%s' found" % serial)
25+ raise ValueError("no disk with serial '%s' found" % serial_udev)
26
27 # Sort by length and take the shortest path name, as the longer path names
28 # will be the partitions on the disk. Then use os.path.realpath to
29@@ -486,7 +492,7 @@
30
31 if not os.path.exists(path):
32 raise ValueError("path '%s' to block device for disk with serial '%s' \
33- does not exist" % (path, serial))
34+ does not exist" % (path, serial_udev))
35 return path
36
37
38
39=== modified file 'curtin/commands/block_meta.py'
40--- curtin/commands/block_meta.py 2016-04-22 16:24:00 +0000
41+++ curtin/commands/block_meta.py 2016-06-24 19:28:31 +0000
42@@ -379,9 +379,14 @@
43 if vol.get('serial'):
44 volume_path = block.lookup_disk(vol.get('serial'))
45 elif vol.get('path'):
46- volume_path = vol.get('path')
47+ # resolve any symlinks to the dev_kname so sys/class/block access
48+ # is valid. ie, there are no udev generated values in sysfs
49+ volume_path = os.path.realpath(vol.get('path'))
50+ elif vol.get('wwn'):
51+ by_wwn = '/dev/disk/by-id/wwn-%s' % vol.get('wwn')
52+ volume_path = os.path.realpath(by_wwn)
53 else:
54- raise ValueError("serial number or path to block dev must be \
55+ raise ValueError("serial, wwn or path to block dev must be \
56 specified to identify disk")
57
58 elif vol.get('type') == "lvm_partition":
59
60=== modified file 'curtin/commands/curthooks.py'
61--- curtin/commands/curthooks.py 2016-06-14 20:57:58 +0000
62+++ curtin/commands/curthooks.py 2016-06-24 19:28:31 +0000
63@@ -638,6 +638,21 @@
64 LOG.info("Detected multipath devices. Installing support via %s", mppkgs)
65
66 util.install_packages(mppkgs, target=target)
67+ replace_spaces = True
68+ try:
69+ # check in-target version
70+ pkg_ver = util.get_package_version('multipath-tools', target=target)
71+ LOG.debug("get_package_version:\n%s", pkg_ver)
72+ LOG.debug("multipath version is %s (major=%s minor=%s micro=%s)",
73+ pkg_ver['semantic_version'], pkg_ver['major'],
74+ pkg_ver['minor'], pkg_ver['micro'])
75+ # multipath-tools versions < 0.5.0 do _NOT_ want whitespace replaced
76+ # i.e. 0.4.X in Trusty.
77+ if pkg_ver['semantic_version'] < 500:
78+ replace_spaces = False
79+ except Exception as e:
80+ LOG.warn("failed reading multipath-tools version, "
81+ "assuming it wants no spaces in wwids: %s", e)
82
83 multipath_cfg_path = os.path.sep.join([target, '/etc/multipath.conf'])
84 multipath_bind_path = os.path.sep.join([target, '/etc/multipath/bindings'])
85@@ -658,7 +673,8 @@
86 if mpbindings or not os.path.isfile(multipath_bind_path):
87 # we do assume that get_devices_for_mp()[0] is /
88 target_dev = block.get_devices_for_mp(target)[0]
89- wwid = block.get_scsi_wwid(target_dev)
90+ wwid = block.get_scsi_wwid(target_dev,
91+ replace_whitespace=replace_spaces)
92 blockdev, partno = block.get_blockdev_for_partition(target_dev)
93
94 mpname = "mpath0"
95
96=== modified file 'curtin/util.py'
97--- curtin/util.py 2016-05-19 18:43:05 +0000
98+++ curtin/util.py 2016-06-24 19:28:31 +0000
99@@ -499,6 +499,62 @@
100 return False
101
102
103+def parse_dpkg_version(raw, name=None, semx=None):
104+ """Parse a dpkg version string into various parts and calcualate a
105+ numerical value of the version for use in comparing package versions
106+
107+ returns a dictionary with the results
108+ """
109+ if semx is None:
110+ semx = (10000, 100, 1)
111+
112+ upstream = raw.split('-')[0]
113+ toks = upstream.split(".", 2)
114+ if len(toks) == 3:
115+ major, minor, micro = toks
116+ elif len(toks) == 2:
117+ major, minor, micro = (toks[0], toks[1], 0)
118+ elif len(toks) == 1:
119+ major, minor, micro = (toks[0], 0, 0)
120+
121+ version = {
122+ 'major': major,
123+ 'minor': minor,
124+ 'micro': micro,
125+ 'raw': raw,
126+ 'upstream': upstream,
127+ }
128+ if name:
129+ version['name'] = name
130+
131+ if semx:
132+ try:
133+ version['semantic_version'] = int(
134+ int(major) * semx[0] + int(minor) * semx[1] +
135+ int(micro) * semx[2])
136+ except (ValueError, IndexError):
137+ version['semantic_version'] = None
138+
139+ return version
140+
141+
142+def get_package_version(pkg, target=None, semx=None):
143+ """Use dpkg-query to extract package pkg's version string
144+ and parse the version string into a dictionary
145+ """
146+ chroot = []
147+ if target is not None:
148+ chroot = ['chroot', target]
149+ try:
150+ out, _ = subp(chroot + ['dpkg-query', '--show', '--showformat',
151+ '${Version}', pkg],
152+ capture=True)
153+ raw = out.rstrip()
154+ return parse_dpkg_version(raw, name=pkg, semx=semx)
155+ except ProcessExecutionError:
156+ return None
157+
158+
159 def find_newer(src, files):
160 mtime = os.stat(src).st_mtime
161 return [f for f in files if
162
163=== added file 'examples/tests/basic_scsi.yaml'
164--- examples/tests/basic_scsi.yaml 1970-01-01 00:00:00 +0000
165+++ examples/tests/basic_scsi.yaml 2016-06-24 19:28:31 +0000
166@@ -0,0 +1,72 @@
167+showtrace: true
168+storage:
169+ version: 1
170+ config:
171+ - id: sda
172+ type: disk
173+ ptable: msdos
174+ wwn: '0x39cc071e72c64cc4'
175+ name: main_disk
176+ wipe: superblock
177+ grub_device: true
178+ - id: sda1
179+ type: partition
180+ number: 1
181+ size: 3GB
182+ device: sda
183+ flag: boot
184+ - id: sda2
185+ type: partition
186+ number: 2
187+ size: 1GB
188+ device: sda
189+ - id: sda1_root
190+ type: format
191+ fstype: ext4
192+ volume: sda1
193+ - id: sda2_home
194+ type: format
195+ fstype: ext4
196+ volume: sda2
197+ - id: sda1_mount
198+ type: mount
199+ path: /
200+ device: sda1_root
201+ - id: sda2_mount
202+ type: mount
203+ path: /home
204+ device: sda2_home
205+ - id: sparedisk_id
206+ type: disk
207+ wwn: '0x080258d13ea95ae5'
208+ name: sparedisk
209+ wipe: superblock
210+ - id: btrfs_disk_id
211+ type: disk
212+ wwn: '0x22dc58dc023c7008'
213+ name: btrfs_volume
214+ wipe: superblock
215+ - id: btrfs_disk_fmt_id
216+ type: format
217+ fstype: btrfs
218+ volume: btrfs_disk_id
219+ - id: btrfs_disk_mnt_id
220+ type: mount
221+ path: /btrfs
222+ device: btrfs_disk_fmt_id
223+ - id: pnum_disk
224+ type: disk
225+ wwn: '0x550a270c3a5811c5'
226+ name: pnum_disk
227+ wipe: superblock
228+ ptable: gpt
229+ - id: pnum_disk_p1
230+ type: partition
231+ number: 1
232+ size: 1GB
233+ device: pnum_disk
234+ - id: pnum_disk_p2
235+ type: partition
236+ number: 10
237+ size: 1GB
238+ device: pnum_disk
239
240=== added file 'examples/tests/multipath.yaml'
241--- examples/tests/multipath.yaml 1970-01-01 00:00:00 +0000
242+++ examples/tests/multipath.yaml 2016-06-24 19:28:31 +0000
243@@ -0,0 +1,38 @@
244+showtrace: true
245+storage:
246+ version: 1
247+ config:
248+ - id: sda
249+ type: disk
250+ ptable: msdos
251+ serial: 'IPR-0 1234567890'
252+ name: mpath_a
253+ wipe: superblock
254+ grub_device: true
255+ - id: sda1
256+ type: partition
257+ number: 1
258+ size: 3GB
259+ device: sda
260+ flag: boot
261+ - id: sda2
262+ type: partition
263+ number: 2
264+ size: 1GB
265+ device: sda
266+ - id: sda1_root
267+ type: format
268+ fstype: ext4
269+ volume: sda1
270+ - id: sda2_home
271+ type: format
272+ fstype: ext4
273+ volume: sda2
274+ - id: sda1_mount
275+ type: mount
276+ path: /
277+ device: sda1_root
278+ - id: sda2_mount
279+ type: mount
280+ path: /home
281+ device: sda2_home
282
283=== modified file 'tests/vmtests/__init__.py'
284--- tests/vmtests/__init__.py 2016-06-20 18:11:20 +0000
285+++ tests/vmtests/__init__.py 2016-06-24 19:28:31 +0000
286@@ -48,6 +48,7 @@
287
288
289 DEFAULT_BRIDGE = os.environ.get("CURTIN_VMTEST_BRIDGE", "user")
290+OUTPUT_DISK_NAME = 'output_disk.img'
291
292 _TOPDIR = None
293
294@@ -333,7 +334,7 @@
295
296 # create output disk, mount ro
297 logger.debug('Creating output disk')
298- self.output_disk = os.path.join(self.boot, "output_disk.img")
299+ self.output_disk = os.path.join(self.boot, OUTPUT_DISK_NAME)
300 subprocess.check_call(["qemu-img", "create", "-f", TARGET_IMAGE_FORMAT,
301 self.output_disk, "10M"],
302 stdout=DEVNULL, stderr=subprocess.STDOUT)
303@@ -350,20 +351,23 @@
304 class VMBaseClass(TestCase):
305 __test__ = False
306 arch_skip = []
307+ boot_timeout = 300
308+ collect_scripts = []
309+ conf_file = "examples/tests/basic.yaml"
310 disk_block_size = 512
311+ disk_driver = 'virtio-blk'
312 disk_to_check = {}
313+ extra_disks = []
314+ extra_kern_args = None
315 fstab_expected = {}
316- extra_kern_args = None
317- recorded_errors = 0
318- recorded_failures = 0
319 image_store_class = ImageStore
320- collect_scripts = []
321+ install_timeout = 3000
322 interactive = False
323- conf_file = "examples/tests/basic.yaml"
324- extra_disks = []
325+ multipath = False
326+ multipath_num_paths = 2
327 nvme_disks = []
328- boot_timeout = 300
329- install_timeout = 3000
330+ recorded_errors = 0
331+ recorded_failures = 0
332 uefi = False
333
334 # these get set from base_vm_classes
335@@ -446,21 +450,51 @@
336 netdevs.extend(["--netdev=" + DEFAULT_BRIDGE])
337
338 # build disk arguments
339- # --disk source:size:driver:block_size
340- extra_disks = []
341+ disks = []
342+ sc = util.load_file(cls.conf_file)
343+ storage_config = yaml.load(sc).get('storage', {}).get('config', {})
344+ cls.disk_wwns = ["wwn=%s" % x.get('wwn') for x in storage_config
345+ if 'wwn' in x]
346+ cls.disk_serials = ["serial=%s" % x.get('serial')
347+ for x in storage_config if 'serial' in x]
348+
349+ target_disk = "{}:{}:{}:{}:".format(cls.td.target_disk,
350+ "",
351+ cls.disk_driver,
352+ cls.disk_block_size)
353+ if len(cls.disk_wwns):
354+ target_disk += cls.disk_wwns[0]
355+
356+ if len(cls.disk_serials):
357+ target_disk += cls.disk_serials[0]
358+
359+ disks.extend(['--disk', target_disk])
360+
361+ # --disk source:size:driver:block_size:devopts
362 for (disk_no, disk_sz) in enumerate(cls.extra_disks):
363 dpath = os.path.join(cls.td.disks, 'extra_disk_%d.img' % disk_no)
364- extra_disks.extend(
365- ['--disk', '{}:{}:{}:{}'.format(dpath, disk_sz, "",
366- cls.disk_block_size)])
367+ extra_disk = '{}:{}:{}:{}:'.format(dpath, disk_sz,
368+ cls.disk_driver,
369+ cls.disk_block_size)
370+ if len(cls.disk_wwns):
371+ w_index = disk_no + 1
372+ if w_index < len(cls.disk_wwns):
373+ extra_disk += cls.disk_wwns[w_index]
374+
375+ if len(cls.disk_serials):
376+ w_index = disk_no + 1
377+ if w_index < len(cls.disk_serials):
378+ extra_disk += cls.disk_serials[w_index]
379+
380+ disks.extend(['--disk', extra_disk])
381
382 # build nvme disk args if needed
383- nvme_disks = []
384 for (disk_no, disk_sz) in enumerate(cls.nvme_disks):
385 dpath = os.path.join(cls.td.disks, 'nvme_disk_%d.img' % disk_no)
386- nvme_disks.extend(
387- ['--disk', '{}:{}:nvme:{}'.format(dpath, disk_sz,
388- cls.disk_block_size)])
389+ nvme_disk = '{}:{}:nvme:{}:{}'.format(dpath, disk_sz,
390+ cls.disk_block_size,
391+ "serial=nvme-%d" % disk_no)
392+ disks.extend(['--disk', nvme_disk])
393
394 # proxy config
395 configs = [cls.conf_file]
396@@ -485,11 +519,10 @@
397 shutil.copy(OVMF_VARS, nvram)
398 cmd.extend(["--uefi", nvram])
399
400- # --disk source:size:driver:block_size
401- target_disk = "{}:{}:{}:{}".format(cls.td.target_disk, "", "",
402- cls.disk_block_size)
403- cmd.extend(netdevs + ["--disk", target_disk] +
404- extra_disks + nvme_disks +
405+ if cls.multipath:
406+ disks = disks * cls.multipath_num_paths
407+
408+ cmd.extend(netdevs + disks +
409 [boot_img, "--kernel=%s" % boot_kernel, "--initrd=%s" %
410 boot_initrd, "--", "curtin", "-vv", "install"] +
411 ["--config=%s" % f for f in configs] +
412@@ -535,39 +568,58 @@
413 cls.tearDownClass()
414 raise
415
416- # drop the size parameter if present in extra_disks
417- extra_disks = [x if ":" not in x else x.split(':')[0]
418- for x in extra_disks]
419 # create --disk params for nvme disks
420 bsize_args = "logical_block_size={}".format(cls.disk_block_size)
421 bsize_args += ",physical_block_size={}".format(cls.disk_block_size)
422 bsize_args += ",min_io_size={}".format(cls.disk_block_size)
423- disk_driver = "virtio-blk"
424
425 target_disks = []
426- for (disk_no, disk) in enumerate([cls.td.target_disk,
427- cls.td.output_disk]):
428- d = '--disk={},driver={},format={},{}'.format(disk, disk_driver,
429- TARGET_IMAGE_FORMAT,
430- bsize_args)
431- target_disks.extend([d])
432+ for (disk_no, disk) in enumerate([cls.td.target_disk]):
433+ disk = '--disk={},driver={},format={},{}'.format(
434+ disk, cls.disk_driver, TARGET_IMAGE_FORMAT, bsize_args)
435+ if len(cls.disk_wwns):
436+ disk += ",%s" % cls.disk_wwns[0]
437+ if len(cls.disk_serials):
438+ disk += ",%s" % cls.disk_serials[0]
439+
440+ target_disks.extend([disk])
441
442 extra_disks = []
443 for (disk_no, disk_sz) in enumerate(cls.extra_disks):
444 dpath = os.path.join(cls.td.disks, 'extra_disk_%d.img' % disk_no)
445- d = '--disk={},driver={},format={},{}'.format(dpath, disk_driver,
446- TARGET_IMAGE_FORMAT,
447- bsize_args)
448- extra_disks.extend([d])
449+ disk = '--disk={},driver={},format={},{}'.format(
450+ dpath, cls.disk_driver, TARGET_IMAGE_FORMAT, bsize_args)
451+ if len(cls.disk_wwns):
452+ w_index = disk_no + 1
453+ if w_index < len(cls.disk_wwns):
454+ disk += ",%s" % cls.disk_wwns[w_index]
455+
456+ if len(cls.disk_serials):
457+ w_index = disk_no + 1
458+ if w_index < len(cls.disk_serials):
459+ disk += ",%s" % cls.disk_serials[w_index]
460+
461+ extra_disks.extend([disk])
462
463 nvme_disks = []
464 disk_driver = 'nvme'
465 for (disk_no, disk_sz) in enumerate(cls.nvme_disks):
466 dpath = os.path.join(cls.td.disks, 'nvme_disk_%d.img' % disk_no)
467- d = '--disk={},driver={},format={},{}'.format(dpath, disk_driver,
468- TARGET_IMAGE_FORMAT,
469- bsize_args)
470- nvme_disks.extend([d])
471+ disk = '--disk={},driver={},format={},{}'.format(
472+ dpath, disk_driver, TARGET_IMAGE_FORMAT, bsize_args)
473+ nvme_disks.extend([disk])
474+
475+ if cls.multipath:
476+ target_disks = target_disks * cls.multipath_num_paths
477+ extra_disks = extra_disks * cls.multipath_num_paths
478+ nvme_disks = nvme_disks * cls.multipath_num_paths
479+
480+ # output disk is always virtio-blk, with serial of output_disk.img
481+ output_disk = '--disk={},driver={},format={},{},{}'.format(
482+ cls.td.output_disk, 'virtio-blk',
483+ TARGET_IMAGE_FORMAT, bsize_args,
484+ 'serial=%s' % os.path.basename(cls.td.output_disk))
485+ target_disks.extend([output_disk])
486
487 # create xkvm cmd
488 cmd = (["tools/xkvm", "-v", dowait] + netdevs +
489@@ -951,9 +1003,9 @@
490 'content': yaml.dump(base_cloudconfig, indent=1)},
491 {'type': 'text/cloud-config', 'content': ssh_keys}]
492
493+ output_dir = '/mnt/output'
494 output_dir_macro = 'OUTPUT_COLLECT_D'
495- output_dir = '/mnt/output'
496- output_device = '/dev/vdb'
497+ output_device = '/dev/disk/by-id/virtio-%s' % OUTPUT_DISK_NAME
498
499 collect_prep = textwrap.dedent("mkdir -p " + output_dir)
500 collect_post = textwrap.dedent(
501@@ -961,7 +1013,7 @@
502
503 # failsafe poweroff runs on precise only, where power_state does
504 # not exist.
505- precise_poweroff = textwrap.dedent("""#!/bin/sh
506+ precise_poweroff = textwrap.dedent("""#!/bin/sh -x
507 [ "$(lsb_release -sc)" = "precise" ] || exit 0;
508 shutdown -P now "Shutting down on precise"
509 """)
510@@ -971,7 +1023,7 @@
511
512 for part in scripts:
513 if not part.startswith("#!"):
514- part = "#!/bin/sh\n" + part
515+ part = "#!/bin/sh -x\n" + part
516 part = part.replace(output_dir_macro, output_dir)
517 logger.debug('Cloud config archive content (pre-json):' + part)
518 parts.append({'content': part, 'type': 'text/x-shellscript'})
519
520=== modified file 'tests/vmtests/test_basic.py'
521--- tests/vmtests/test_basic.py 2016-06-13 20:49:15 +0000
522+++ tests/vmtests/test_basic.py 2016-06-24 19:28:31 +0000
523@@ -233,3 +233,120 @@
524
525 class YakketyTestBasic(relbase.yakkety, TestBasicAbs):
526 __test__ = True
527+
528+
529+class TestBasicScsiAbs(TestBasicAbs):
530+ conf_file = "examples/tests/basic_scsi.yaml"
531+ disk_driver = 'scsi-hd'
532+ extra_disks = ['128G', '128G', '4G']
533+ nvme_disks = ['4G']
534+ collect_scripts = [textwrap.dedent("""
535+ cd OUTPUT_COLLECT_D
536+ blkid -o export /dev/sda > blkid_output_sda
537+ blkid -o export /dev/sda1 > blkid_output_sda1
538+ blkid -o export /dev/sda2 > blkid_output_sda2
539+ btrfs-show-super /dev/sdc > btrfs_show_super_sdc
540+ cat /proc/partitions > proc_partitions
541+ ls -al /dev/disk/by-uuid/ > ls_uuid
542+ ls -al /dev/disk/by-id/ > ls_disk_id
543+ cat /etc/fstab > fstab
544+ mkdir -p /dev/disk/by-dname
545+ ls /dev/disk/by-dname/ > ls_dname
546+ find /etc/network/interfaces.d > find_interfacesd
547+
548+ v=""
549+ out=$(apt-config shell v Acquire::HTTP::Proxy)
550+ eval "$out"
551+ echo "$v" > apt-proxy
552+ """)]
553+
554+ def test_output_files_exist(self):
555+ self.output_files_exist(
556+ ["blkid_output_sda", "blkid_output_sda1", "blkid_output_sda2",
557+ "btrfs_show_super_sdc", "fstab", "ls_dname", "ls_uuid",
558+ "ls_disk_id", "proc_partitions"])
559+
560+ def test_ptable(self):
561+ blkid_info = self.get_blkid_data("blkid_output_sda")
562+ self.assertEquals(blkid_info["PTTYPE"], "dos")
563+
564+ def test_partition_numbers(self):
565+ # vde should have partitions 1 and 10
566+ disk = "sdd"
567+ proc_partitions_path = os.path.join(self.td.collect,
568+ 'proc_partitions')
569+ self.assertTrue(os.path.exists(proc_partitions_path))
570+ found = []
571+ with open(proc_partitions_path, 'r') as fp:
572+ for line in fp.readlines():
573+ if disk in line:
574+ found.append(line.split()[3])
575+ # /proc/partitions should have 3 lines with 'vde' in them.
576+ expected = [disk + s for s in ["", "1", "10"]]
577+ self.assertEqual(found, expected)
578+
579+ def test_partitions(self):
580+ with open(os.path.join(self.td.collect, "fstab")) as fp:
581+ fstab_lines = fp.readlines()
582+ print("\n".join(fstab_lines))
583+ # Test that vda1 is on /
584+ blkid_info = self.get_blkid_data("blkid_output_sda1")
585+ fstab_entry = None
586+ for line in fstab_lines:
587+ if blkid_info['UUID'] in line:
588+ fstab_entry = line
589+ break
590+ self.assertIsNotNone(fstab_entry)
591+ self.assertEqual(fstab_entry.split(' ')[1], "/")
592+
593+ # Test that vda2 is on /home
594+ blkid_info = self.get_blkid_data("blkid_output_sda2")
595+ fstab_entry = None
596+ for line in fstab_lines:
597+ if blkid_info['UUID'] in line:
598+ fstab_entry = line
599+ break
600+ self.assertIsNotNone(fstab_entry)
601+ self.assertEqual(fstab_entry.split(' ')[1], "/home")
602+
603+ # Test whole disk sdc is mounted at /btrfs
604+ fstab_entry = None
605+ for line in fstab_lines:
606+ if "/dev/sdc" in line:
607+ fstab_entry = line
608+ break
609+ self.assertIsNotNone(fstab_entry)
610+ self.assertEqual(fstab_entry.split(' ')[1], "/btrfs")
611+
612+ def test_whole_disk_format(self):
613+ # confirm the whole disk format is the expected device
614+ with open(os.path.join(self.td.collect,
615+ "btrfs_show_super_sdc"), "r") as fp:
616+ btrfs_show_super = fp.read()
617+
618+ with open(os.path.join(self.td.collect, "ls_uuid"), "r") as fp:
619+ ls_uuid = fp.read()
620+
621+ # extract uuid from btrfs superblock
622+ btrfs_fsid = [line for line in btrfs_show_super.split('\n')
623+ if line.startswith('fsid\t\t')]
624+ self.assertEqual(len(btrfs_fsid), 1)
625+ btrfs_uuid = btrfs_fsid[0].split()[1]
626+ self.assertTrue(btrfs_uuid is not None)
627+
628+ # extract uuid from /dev/disk/by-uuid on /dev/sdc
629+ # parsing ls -al output on /dev/disk/by-uuid:
630+ # lrwxrwxrwx 1 root root 9 Dec 4 20:02
631+ # d591e9e9-825a-4f0a-b280-3bfaf470b83c -> ../../vdg
632+ uuid = [line.split()[8] for line in ls_uuid.split('\n')
633+ if 'sdc' in line]
634+ self.assertEqual(len(uuid), 1)
635+ uuid = uuid.pop()
636+ self.assertTrue(uuid is not None)
637+
638+ # compare them
639+ self.assertEqual(uuid, btrfs_uuid)
640+
641+
642+class XenialTestScsiBasic(relbase.xenial, TestBasicScsiAbs):
643+ __test__ = True
644
645=== added file 'tests/vmtests/test_multipath.py'
646--- tests/vmtests/test_multipath.py 1970-01-01 00:00:00 +0000
647+++ tests/vmtests/test_multipath.py 2016-06-24 19:28:31 +0000
648@@ -0,0 +1,63 @@
649+from . import VMBaseClass
650+from .releases import base_vm_classes as relbase
651+
652+import os
653+import textwrap
654+
655+
656+class TestMultipathBasicAbs(VMBaseClass):
657+ conf_file = "examples/tests/multipath.yaml"
658+ multipath = True
659+ disk_driver = 'scsi-hd'
660+ extra_disks = []
661+ nvme_disks = []
662+ collect_scripts = [textwrap.dedent("""
663+ cd OUTPUT_COLLECT_D
664+ blkid -o export /dev/sda > blkid_output_sda
665+ blkid -o export /dev/sda1 > blkid_output_sda1
666+ blkid -o export /dev/sda2 > blkid_output_sda2
667+ blkid -o export /dev/sdb > blkid_output_sdb
668+ blkid -o export /dev/sdb1 > blkid_output_sdb1
669+ blkid -o export /dev/sdb2 > blkid_output_sdb2
670+ dmsetup ls > dmsetup_ls
671+ dmsetup info > dmsetup_info
672+ cat /proc/partitions > proc_partitions
673+ multipath -ll > multipath_ll
674+ multipath -v3 -ll > multipath_v3_ll
675+ multipath -r > multipath_r
676+ cp -a /etc/multipath* .
677+ ls -al /dev/disk/by-uuid/ > ls_uuid
678+ ls -al /dev/disk/by-id/ > ls_disk_id
679+ readlink -f /sys/class/block/sda/holders/dm-0 > holders_sda
680+ readlink /sys/class/block/sdb/holders/dm-0 > holders_sdb
681+ cat /etc/fstab > fstab
682+ mkdir -p /dev/disk/by-dname
683+ ls /dev/disk/by-dname/ > ls_dname
684+ find /etc/network/interfaces.d > find_interfacesd
685+ """)]
686+
687+ def test_multipath_disks_match(self):
688+ sda = os.path.join(self.td.collect, 'holders_sda')
689+ sdb = os.path.join(self.td.collect, 'holders_sdb')
690+ self.assertTrue(os.path.exists(sda))
691+ self.assertTrue(os.path.exists(sdb))
692+ with open(sda, 'r') as fp:
693+ sda_data = fp.read()
694+ print('sda holders:\n%s' % sda_data)
695+ with open(sda, 'r') as fp:
696+ sdb_data = fp.read()
697+ print('sdb holders:\n%s' % sda_data)
698+
699+ self.assertEqual(sda_data, sdb_data)
700+
701+
702+class TrustyTestMultipathBasic(relbase.trusty, TestMultipathBasicAbs):
703+ __test__ = True
704+
705+
706+class XenialTestMultipathBasic(relbase.xenial, TestMultipathBasicAbs):
707+ __test__ = True
708+
709+
710+class YakketyTestMultipathBasic(relbase.yakkety, TestMultipathBasicAbs):
711+ __test__ = True
712
713=== modified file 'tools/launch'
714--- tools/launch 2016-04-08 03:14:21 +0000
715+++ tools/launch 2016-06-24 19:28:31 +0000
716@@ -1,4 +1,4 @@
717-#!/bin/bash
718+#!/bin/bash -x
719
720 VERBOSITY=0
721 TEMP_D=""
722@@ -23,11 +23,16 @@
723 --add F[:T] add file 'F' to the curtin archive at T
724 -a | --append append args to kernel cmdline (--kernel)
725 -A | --arch A assume guest kernel architecture A
726- -d | --disk D add a disk 'D' format (path[:size][:driver][:bsize])
727+ -d | --disk D add a disk 'D' format
728+ (path[:size][:driver][:bsize][:devopts])
729 driver can be specified without size using path::driver
730 driver defaults to virtio-blk
731 bsize <logical>[,<physical>][,<min_io_size>]
732 bsize defaults to 512b sector size
733+ opts is a comma delimitted list of property=value
734+ elements. Examine qemu-kvm -device scsi-hd,? for
735+ details.
736+ --vnc D use -vnc D (mutually exclusive with --silent)
737 --uefi N enable uefi boot method, store nvram at N
738 -h | --help show this message
739 -i | --initrd F use initramfs F
740@@ -280,7 +285,7 @@
741 case "$cur" in
742 --add) addfiles[${#addfiles[@]}]="$next"; shift;;
743 -a|--append) uappend="$next"; shift;;
744- -A|--arch) arch_hint="$next"; shift;;
745+ -A|--arch) arch_hint="$next"; shift;;
746 -d|--disk) disks[${#disks[@]}]="$next"; shift;;
747 -h|--help) Usage ; exit 0;;
748 -i|--initrd) initrd="$next"; shift;;
749@@ -388,20 +393,21 @@
750 { error "failed to get dir for $0"; return 1; }
751
752 local disk="" src="" size="" fmt="" out="" id="" driver="" if=""
753- local split_input=""
754+ local split_input="" serial=""
755 disk_args=( )
756 id=1
757 for disk in "${disks[@]}"; do
758 ((id++))
759-
760 # 1=src
761 # 2=src:size
762 # 3=src:size:driver
763 # 4=src:size:driver:bsize
764+ # 5=src:size:driver:bsize:devopts
765 src=$(echo $disk | awk -F: '{print $1}')
766 size=$(echo $disk | awk -F: '{print $2}')
767 driver=$(echo $disk | awk -F: '{print $3}')
768 bsize=$(echo $disk | awk -F: '{print $4}')
769+ devopts=$(echo $disk | awk -F: '{print $5}')
770
771 if [ -z "${src}" ]; then
772 error "Failed to provide disk source"
773@@ -430,6 +436,11 @@
774 { error "failed to determine format of $src"; return 1; }
775 fi
776
777+ # prepend comma if passing devopts
778+ if [ -n "${devopts}" ]; then
779+ devopts=",${devopts}"
780+ fi
781+
782 # set logical/physical size blocksz is logical:phys
783 local logbs=$(round_up ${bsize%%:*})
784 local phybs=$(round_up ${bsize##*:})
785@@ -441,7 +452,7 @@
786 "file=${src},if=none,cache=unsafe,format=$fmt,id=drv${id},index=$id" )
787
788 disk_args=( "${disk_args[@]}" "-device"
789- "${driver},drive=drv${id},serial=dev${id},${bs_args}" )
790+ "${driver},drive=drv${id},${bs_args}${devopts}" )
791
792 done
793
794@@ -557,7 +568,9 @@
795 # if this is a partition image, root=/dev/vda. else root=/dev/vda1
796 # this hack is necessary because LABEL even UUID might be the same
797 # in the boot image and the target (if re-using target)
798- if tmp=$(blkid "$bootimg_dist" -ovalue -s UUID) && [ -n "$tmp" ]; then
799+ local bottom=$(qemu-img info --backing-chain "$bootimg_dist" |
800+ grep ^image | tail -n1 | awk '{print $2}')
801+ if tmp=$(blkid "$bottom" -ovalue -s UUID) && [ -n "$tmp" ]; then
802 root="/dev/vda"
803 else
804 root="/dev/vda1"
805@@ -586,17 +599,19 @@
806 local cmd serial_args="" chardev_arg=""
807 [ "${serial_log}" = "none" ] && serial_log=""
808 if [ -n "${serial_log}" ]; then
809- if [ "${arch_hint}" = "s390x" ]; then
810- if [ "${serial_log}" = "stdio" ]; then
811- chardev_arg="stdio"
812- else
813- chardev_arg="file,path=${serial_log}"
814- fi
815- serial_args="-nodefaults -chardev ${chardev_arg},id=charconsole0 -device sclpconsole,chardev=charconsole0,id=console0"
816- else
817- serial_args="-serial file:${serial_log}"
818- fi
819+ if [ "${arch_hint}" = "s390x" ]; then
820+ if [ "${serial_log}" = "stdio" ]; then
821+ chardev_arg="stdio"
822+ else
823+ chardev_arg="file,path=${serial_log}"
824+ fi
825+ serial_args="-nodefaults -chardev ${chardev_arg},id=charconsole0 -device sclpconsole,chardev=charconsole0,id=console0"
826+ else
827+ serial_args="-serial file:${serial_log}"
828+ #debug mode serial_args="-serial ${serial_log} -monitor stdio"
829+ fi
830 fi
831+ # -monitor stdio
832 cmd=(
833 xkvm "${pt[@]}" "${netargs[@]}" --
834 "${bios_opts[@]}"
835@@ -625,6 +640,11 @@
836 return $ret
837 }
838
839+random_wwn() {
840+ # wwn must be a int64, less than (1 << 63) - 1
841+ # we achieve this by combining 4 (1 << 15) ints
842+ printf "0x%04x%04x%04x%04x" $RANDOM $RANDOM $RANDOM $RANDOM
843+}
844
845 round_up() {
846 local size="${1}"

Subscribers

People subscribed via source and target branches