Merge lp:~raharper/curtin/trunk.vmtest-multipath into lp:~curtin-dev/curtin/trunk
- trunk.vmtest-multipath
- Merge into trunk
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 |
Related bugs: |
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/
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/
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.
Server Team CI bot (server-team-bot) wrote : | # |
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.
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_
Yours: => http://
Mine as reference: => http://
You can give it a look yourself it is on ubuntu@
I imported your LP id to ubuntu@
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.
> 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
Ryan Harper (raharper) 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.
>
Thanks for the review Christian. Picking up most of your suggestions.
A few things will need to put off for now.
> > === modified file 'curtin/
> > --- curtin/
> > +++ curtin/
> > @@ -356,6 +356,7 @@
> > cmd.append(
> > 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.ProcessExe
> will read the debug level info.
>
OK
> > scsi_wwid = out.rstrip('\n')
> > return scsi_wwid
> > except util.ProcessExe
> >
> > === modified file 'curtin/
> > --- curtin/
> > +++ curtin/
> > @@ -379,9 +379,14 @@
> > if vol.get('serial'):
> > volume_path = block.lookup_
> > 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.
>
> 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/
> > --- curtin/
> > +++ curtin/
> > @@ -638,6 +638,20 @@
> > LOG.info("Detected multipath devices. Installing support via %s", mppkgs)
> >
> > util.install_
> > + replace_spaces = True
> > + try:
> > + # check in-target version
> > + pkg_ver = util.get_
> > + upstream = pkg_ver.
> > + 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.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:399
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
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://
i'd like that fixed, and then please merge. whoowho!
- 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
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:402
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Preview Diff
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}" |
PASSED: Continuous integration, rev:392 /server- team-jenkins. canonical. com/job/ curtin- ci/250/ /server- team-jenkins. canonical. com/job/ generic- update- mp/247/ console
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild: /server- team-jenkins. canonical. com/job/ curtin- ci/250/ rebuild
https:/