Merge lp:~salgado/linaro-image-tools/partition-setup into lp:linaro-image-tools/11.11
- partition-setup
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 192 |
Proposed branch: | lp:~salgado/linaro-image-tools/partition-setup |
Merge into: | lp:linaro-image-tools/11.11 |
Diff against target: |
1022 lines (+472/-230) 10 files modified
README (+2/-0) linaro-media-create (+11/-89) media_create/cmd_runner.py (+9/-5) media_create/create_partitions.py (+26/-45) media_create/partition_size.py (+0/-42) media_create/partitions.py (+193/-0) media_create/populate_boot.py (+9/-5) media_create/tests/fixtures.py (+14/-3) media_create/tests/test_media_create.py (+183/-41) tests/integration.txt (+25/-0) |
To merge this branch: | bzr merge lp:~salgado/linaro-image-tools/partition-setup |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Ohlsson (community) | Approve | ||
Review via email: mp+43520@code.launchpad.net |
Commit message
Description of the change
This is a rather large branch with lots of changes that were needed to port
some shell code that was interrelated and thus needed to be ported at once.
- Change cmd_runner.run() to return a Popen instance so that it's more
flexible
- Simplify create_partitions now that it's only called from python code
- Create a new partitions.py module which provides a single function for doing
everything related with partitioning. That's where most of the new code is,
but everything should be self-explanatory (or have comments) there.
- Merge partition_size.py into the above file
- Extend MockCmdRunnerPopen so that it stores the arguments passed in to each
of its invocations; useful when testing functions that run multiple
subprocesses.
- Create a new tests/integrati
tests that are useful but must not be automated.
Martin Ohlsson (martin-ohlson) wrote : | # |
Peter Maydell (pmaydell) wrote : | # |
146 - # Round the size of the raw disk image up to a multiple of 256K
147 - # so it is an exact number of SD card erase blocks in length.
148 - # Otherwise Linux under qemu cannot access the last part of the
149 - # card and is likely to complain that the last partition on the
150 - # disk has been truncated.
151 - IMAGE_SIZE=
So this bug fix has gone from the shell script but I can't find the bit in the Python which implements it. Am I missing something?
Guilherme Salgado (salgado) wrote : | # |
On Mon, 2010-12-13 at 16:25 +0000, Martin Ohlsson wrote:
> > === modified file 'media_
> > --- media_create/
> > +++ media_create/
> >
> > cmd_runner.run(
> > - ['parted', '-s', device, 'mklabel', 'msdos'], as_root=True)
> > + ['parted', '-s', media.path, 'mklabel', 'msdos'], as_root=True)
> > + # It sems to be necessary to sleep a bit here to avoid a race
> > + # condition with the sfdisk commands executed below. Try removing it
> > + # and running the integration tests to see how it fails.
> > + time.sleep(0.5)
>
> Isn't it possible to wait() for the parted command to finish?
Very good catch! Friday I updated a bunch of callsites that were
expecting cmd_runner.run() to wait() for them but forgot this one and
today it didn't occur to me that the race could be a consequence of the
now-missing wait() call. I've updated this and a few other callsites
that I missed to update when I changed cmd_runner.run(). Thanks!
- 193. By Guilherme Salgado
-
Update a few cmd_runner.run() callsites that were not wait()ing
Guilherme Salgado (salgado) wrote : | # |
On Mon, 2010-12-13 at 16:39 +0000, Peter Maydell wrote:
> 146 - # Round the size of the raw disk image up to a multiple of 256K
> 147 - # so it is an exact number of SD card erase blocks in length.
> 148 - # Otherwise Linux under qemu cannot access the last part of the
> 149 - # card and is likely to complain that the last partition on the
> 150 - # disk has been truncated.
> 151 - IMAGE_SIZE=
>
> So this bug fix has gone from the shell script but I can't find the bit in the Python which implements it. Am I missing something
Looks like it was lost in translation indeed. However, qemu was able to
boot an image I generated using the code on this branch. Would the lack
of this fix render the image unusable or should I expect to see other
symptoms? Is there any chance the fix is no longer needed or is needed
only for certain versions of qemu?
Peter Maydell (pmaydell) wrote : | # |
The symptom would be that on bootup fsck complains about the filesystem being larger than the partition. It may well happen only for certain image sizes -- if you happened to get l-m-c to use an image size that's already a multiple of 256K then you wouldn't see any effect.
(We've also had problems reported even with the bugfix, eg https:/
However, I think it would be better to deal with possible improvements to that bug fix elsewhere, not as part of this conversion-
James Westby (james-w) wrote : | # |
Hi,
I haven't read this in the detail that I would like, but I couldn't see any issues.
Could I suggest that your integration tests tell you what the expected result should
be?
Thanks,
James
Martin Ohlsson (martin-ohlson) wrote : | # |
Hi Guilherme,
I read the code a couple of times without noticing anything strange besides my previous comment.
/Martin
Guilherme Salgado (salgado) wrote : | # |
On Mon, 2010-12-13 at 17:37 +0000, Peter Maydell wrote:
> The symptom would be that on bootup fsck complains about the
> filesystem being larger than the partition. It may well happen only
> for certain image sizes -- if you happened to get l-m-c to use an
> image size that's already a multiple of 256K then you wouldn't see any
> effect.
>
> (We've also had problems reported even with the bugfix, eg
> https:/
> still not quite right.)
>
> However, I think it would be better to deal with possible improvements
> to that bug fix elsewhere, not as part of this conversion-
> process.
Agreed, but if I knew how to reproduce I could add that as a comment in
the code to save time when somebody gets to work on those specific bits.
The bug-fix in now present in the python version.
- 194. By Guilherme Salgado
-
A few tweaks a bug fixes suggested by reviewers
Preview Diff
1 | === modified file 'README' |
2 | --- README 2010-12-03 13:22:03 +0000 |
3 | +++ README 2010-12-14 13:31:33 +0000 |
4 | @@ -11,6 +11,8 @@ |
5 | - python-debian >= 0.1.16ubuntu1 |
6 | - python-argparse |
7 | - dpkg-dev |
8 | + - python-parted |
9 | + - python-dbus |
10 | |
11 | And run the following command: |
12 | |
13 | |
14 | === modified file 'linaro-media-create' |
15 | --- linaro-media-create 2010-12-12 20:48:29 +0000 |
16 | +++ linaro-media-create 2010-12-14 13:31:33 +0000 |
17 | @@ -34,7 +34,7 @@ |
18 | MMC_PART_OFFSET=0 |
19 | BOOTFS_STEP="create_boot_cmd populate_boot" |
20 | ROOTFS_STEP="populate_rootfs" |
21 | -PARTITION_STEP="create_partitions" |
22 | +SHOULD_CREATE_PARTITIONS="yes" |
23 | mmc_option="0:1" |
24 | boot_args_options="rootwait ro" |
25 | HWPACK_FORCE_YES="no" |
26 | @@ -272,13 +272,13 @@ |
27 | BOOTFS_STEP="" |
28 | ;; |
29 | --no-part) |
30 | - PARTITION_STEP="" |
31 | + SHOULD_CREATE_PARTITIONS="no" |
32 | ;; |
33 | esac |
34 | shift |
35 | done |
36 | |
37 | -DEPLOY_STEPS="$PARTITION_STEP prepare_partitions $BOOTFS_STEP $ROOTFS_STEP" |
38 | +DEPLOY_STEPS="setup_partitions $BOOTFS_STEP $ROOTFS_STEP" |
39 | |
40 | python -m media_create.ensure_command mkimage uboot-mkimage |
41 | python -m media_create.ensure_command uuidgen uuid-runtime |
42 | @@ -309,33 +309,6 @@ |
43 | |
44 | RFS_UUID=`uuidgen -r` |
45 | |
46 | -get_device_by_id() { |
47 | - if [ ! "${IMAGE_FILE}" ]; then |
48 | - for device in /dev/disk/by-id/*; do |
49 | - if [ `realpath $device` = $DEVICE ]; then |
50 | - case "$device" in |
51 | - *-part[0-9]*) |
52 | - echo "device $DEVICE must not be a partition part ($device)" >&2 |
53 | - exit 1 |
54 | - ;; |
55 | - esac |
56 | - for part_id in `ls "$device-part"*`; do |
57 | - part=`realpath $part_id` |
58 | - part_no=`echo $part_id | sed -e 's/.*-part//g'` |
59 | - # echo "part $part_no found: $part_id" 1>&2 |
60 | - # if there is a non-fs-data part (e.g. mx51evk), MMC_PART_OFFSET=1 |
61 | - if test "$part_no" = $((1 + $MMC_PART_OFFSET)); then |
62 | - BOOTFS=$part |
63 | - elif test "$part_no" = $((2 + $MMC_PART_OFFSET)) ; then |
64 | - ROOTFS=$part |
65 | - fi |
66 | - done |
67 | - break |
68 | - fi |
69 | - done |
70 | - fi |
71 | -} |
72 | - |
73 | install_hwpack() { |
74 | chroot=${DIR}/binary |
75 | # Make sure we unmount /proc in the chroot or else it can't be moved to the |
76 | @@ -445,33 +418,22 @@ |
77 | loop_devices="$loop_devices $1" |
78 | } |
79 | |
80 | -create_partitions() { |
81 | - python -m media_create.create_partitions "${DEVIMAGE}" \ |
82 | - "${DEVICE-$IMAGE_FILE}" "$FAT_SIZE" "$HEADS" "$SECTORS" "$CYLINDER_ARG" |
83 | +setup_partitions() { |
84 | + boot_and_root_devices=$(python -m media_create.partitions \ |
85 | + "$DEVIMAGE" "${DEVICE-$IMAGE_FILE}" "$FAT_SIZE" "$IMAGE_SIZE" \ |
86 | + "$SHOULD_CREATE_PARTITIONS") |
87 | + eval $boot_and_root_devices |
88 | |
89 | if [ "${IMAGE_FILE}" ]; then |
90 | - sizes_and_offsets=$(python -m media_create.partition_size "${IMAGE_FILE}") |
91 | - eval $sizes_and_offsets |
92 | - BOOTFS=$(sudo losetup -f --show "$IMAGE_FILE" --offset $VFATOFFSET \ |
93 | - --sizelimit $VFATSIZE) |
94 | register_loopback "$BOOTFS" |
95 | - ROOTFS=$(sudo losetup -f --show "$IMAGE_FILE" --offset $ROOTOFFSET \ |
96 | - --sizelimit $ROOTSIZE) |
97 | register_loopback "$ROOTFS" |
98 | fi |
99 | + |
100 | + prepare_partitions |
101 | } |
102 | |
103 | - |
104 | prepare_partitions() { |
105 | |
106 | - # sync and sleep a bit as the partitions may have been created in the |
107 | - # previous step. |
108 | - echo -n "waiting for partitioning to settle ..." |
109 | - sync |
110 | - sleep 3 |
111 | - echo "done." |
112 | - get_device_by_id |
113 | - |
114 | if [ "$BOOTFS_STEP" ]; then |
115 | echo "" |
116 | echo "Formating Boot Partition" |
117 | @@ -565,6 +527,7 @@ |
118 | echo "Creating /etc/flash-kernel.conf" |
119 | echo "" |
120 | |
121 | + TARGET_BOOT_DEV=/dev/mmcblk0p$(( 1 + $MMC_PART_OFFSET )) |
122 | echo "UBOOT_PART=${TARGET_BOOT_DEV}" | sudo tee "${ROOT_DISK}/etc/flash-kernel.conf" >/dev/null |
123 | |
124 | sync |
125 | @@ -572,39 +535,6 @@ |
126 | sudo umount "${ROOT_DISK}" || true |
127 | } |
128 | |
129 | -calculatesize() { |
130 | - IMAGE_SIZE=${IMAGE_SIZE/G/M*1024} |
131 | - IMAGE_SIZE=${IMAGE_SIZE/M/K*1024} |
132 | - IMAGE_SIZE=${IMAGE_SIZE/K/*1024} |
133 | - IMAGE_SIZE=$(($IMAGE_SIZE)) |
134 | -} |
135 | - |
136 | -setup_sizes() { |
137 | - HEADS=255 |
138 | - SECTORS=63 |
139 | - SECTORSIZE=512 |
140 | - if [ "${IMAGE_FILE}" ]; then |
141 | - calculatesize |
142 | - CYLINDERSIZE=$(($HEADS*$SECTORS*$SECTORSIZE)) |
143 | - CYLINDERS=$(($IMAGE_SIZE/$CYLINDERSIZE)) |
144 | - CYLINDER_ARG="-C $CYLINDERS" |
145 | - IMAGE_SIZE=$(($CYLINDERS*$CYLINDERSIZE)) |
146 | - # Round the size of the raw disk image up to a multiple of 256K |
147 | - # so it is an exact number of SD card erase blocks in length. |
148 | - # Otherwise Linux under qemu cannot access the last part of the |
149 | - # card and is likely to complain that the last partition on the |
150 | - # disk has been truncated. |
151 | - IMAGE_SIZE=$(((($IMAGE_SIZE-1)/(1024*256)+1)*(1024*256))) |
152 | - else |
153 | - CYLINDER_ARG="" |
154 | - fi |
155 | -} |
156 | - |
157 | -# XXX: Apparently there's no need to run qemu-img as root. |
158 | -setup_image() { |
159 | - sudo qemu-img create -f raw "$IMAGE_FILE" $IMAGE_SIZE |
160 | -} |
161 | - |
162 | remove_image_file() { |
163 | if [ -f "$IMAGE_FILE" ]; then |
164 | rm -v $IMAGE_FILE |
165 | @@ -659,11 +589,6 @@ |
166 | trap cleanup_handler EXIT |
167 | trap signal_handler INT TERM |
168 | |
169 | -setup_sizes |
170 | -if [ "${IMAGE_FILE}" ]; then |
171 | - setup_image |
172 | -fi |
173 | - |
174 | serial_opts="" |
175 | if [ "$consoles" ]; then |
176 | for c in ${consoles}; do |
177 | @@ -767,9 +692,6 @@ |
178 | fi |
179 | fi |
180 | |
181 | -TARGET_BOOT_DEV=/dev/mmcblk0p$(( 1 + $MMC_PART_OFFSET )) |
182 | -TARGET_ROOT_DEV=/dev/mmcblk0p$(( 2 + $MMC_PART_OFFSET )) |
183 | - |
184 | if [ ! "${DEVICE}" -a ! "${IMAGE_FILE}" ]; then |
185 | usage |
186 | fi |
187 | |
188 | === modified file 'media_create/cmd_runner.py' |
189 | --- media_create/cmd_runner.py 2010-12-09 19:17:14 +0000 |
190 | +++ media_create/cmd_runner.py 2010-12-14 13:31:33 +0000 |
191 | @@ -1,13 +1,19 @@ |
192 | import subprocess |
193 | |
194 | |
195 | -def run(args, as_root=False, stdout=None): |
196 | +def run(args, as_root=False, stdin=None, stdout=None, stderr=None): |
197 | """Run the given command as a sub process. |
198 | |
199 | + Return a Popen instance. |
200 | + |
201 | + Callsites must wait() or communicate() with the returned Popen instance. |
202 | + |
203 | :param command: A list or tuple containing the command to run and the |
204 | arguments that should be passed to it. |
205 | :param as_root: Should the given command be run as root (with sudo)? |
206 | - :param stdout: Same as subprocess.Popen(). |
207 | + :param stdin: Same as in subprocess.Popen(). |
208 | + :param stdout: Same as in subprocess.Popen(). |
209 | + :param stderr: Same as in subprocess.Popen(). |
210 | """ |
211 | assert isinstance(args, (list, tuple)), ( |
212 | "The command to run must be a list or tuple, found: %s" % type(args)) |
213 | @@ -16,9 +22,7 @@ |
214 | if as_root: |
215 | args = args[:] |
216 | args.insert(0, 'sudo') |
217 | - proc = Popen(args, stdout=stdout) |
218 | - proc.wait() |
219 | - return proc.returncode |
220 | + return Popen(args, stdin=stdin, stdout=stdout, stderr=stderr) |
221 | |
222 | |
223 | class Popen(subprocess.Popen): |
224 | |
225 | === modified file 'media_create/create_partitions.py' |
226 | --- media_create/create_partitions.py 2010-12-09 19:58:24 +0000 |
227 | +++ media_create/create_partitions.py 2010-12-14 13:31:33 +0000 |
228 | @@ -1,11 +1,12 @@ |
229 | import subprocess |
230 | -import sys |
231 | +import time |
232 | |
233 | from media_create import cmd_runner |
234 | |
235 | |
236 | -def run_sfdisk_commands(commands, heads, sectors, cylinders_arg, device, |
237 | - as_root=True): |
238 | +# TODO: Merge this file with setup_partitions.py. |
239 | +def run_sfdisk_commands(commands, heads, sectors, cylinders, device, |
240 | + as_root=True, stderr=None): |
241 | """Run the given commands under sfdisk. |
242 | |
243 | :param commands: A string of sfdisk commands; each on a separate line. |
244 | @@ -15,41 +16,33 @@ |
245 | '-D', |
246 | '-H', str(heads), |
247 | '-S', str(sectors)] |
248 | - if cylinders_arg: |
249 | - args.append(cylinders_arg) |
250 | + if cylinders is not None: |
251 | + args.extend(['-C', str(cylinders)]) |
252 | args.append(device) |
253 | - # XXX: There's some stuff duplicated here from cmd_runner.run() but I |
254 | - # don't see an easy way to consolidate them as a single function, so I'll |
255 | - # leave it for later. |
256 | - if as_root: |
257 | - args = args[:] |
258 | - args.insert(0, 'sudo') |
259 | - proc = cmd_runner.Popen( |
260 | - args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, |
261 | - stderr=subprocess.PIPE) |
262 | + proc = cmd_runner.run( |
263 | + args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=stderr, |
264 | + as_root=as_root) |
265 | return proc.communicate("%s\n" % commands) |
266 | |
267 | |
268 | -def create_partitions(board, device, fat_size, heads, sectors, cylinders_arg): |
269 | - """Partition the given device according to the board requirements. |
270 | +def create_partitions(board, media, fat_size, heads, sectors, cylinders=None): |
271 | + """Partition the given media according to the board requirements. |
272 | |
273 | :param board: A string with the board type (e.g. beagle, panda, etc) |
274 | - :param device: A string containing the path to the device to partition. |
275 | + :param media: A setup_partitions.Media object to partition. |
276 | :param fat_size: The type of FATs used in the boot partition (16 or 32). |
277 | :param heads: Number of heads to use in the disk geometry of |
278 | partitions. |
279 | :param sectors: Number of sectors to use in the disk geometry of |
280 | partitions. |
281 | - :param cylinders_arg: A string of the form "-C NN" containing the number |
282 | - of cylinders to use in the disk geometry of partitions. |
283 | + :param cylinders: The number of cylinders to pass to sfdisk's -C argument. |
284 | + If None the -C argument is not passed. |
285 | """ |
286 | - stdout = [] |
287 | - stderr = [] |
288 | - is_block_device = device.startswith('/dev/') |
289 | - if is_block_device: |
290 | + if media.is_block_device: |
291 | # Overwrite any existing partition tables with a fresh one. |
292 | - cmd_runner.run( |
293 | - ['parted', '-s', device, 'mklabel', 'msdos'], as_root=True) |
294 | + proc = cmd_runner.run( |
295 | + ['parted', '-s', media.path, 'mklabel', 'msdos'], as_root=True) |
296 | + proc.wait() |
297 | |
298 | if fat_size == 32: |
299 | partition_type = '0x0C' |
300 | @@ -60,28 +53,16 @@ |
301 | # Create a one cylinder partition for fixed-offset bootloader data at |
302 | # the beginning of the image (size is one cylinder, so 8224768 bytes |
303 | # with the first sector for MBR). |
304 | - out, err = run_sfdisk_commands( |
305 | - ',1,0xDA', heads, sectors, cylinders_arg, device) |
306 | - stdout.append(out) |
307 | - stderr.append(err) |
308 | + run_sfdisk_commands(',1,0xDA', heads, sectors, cylinders, media.path) |
309 | |
310 | # Create a VFAT or FAT16 partition of 9 cylinders (74027520 bytes, ~70 |
311 | # MiB), followed by a Linux-type partition containing the rest of the free |
312 | # space. |
313 | sfdisk_cmd = ',9,%s,*\n,,,-' % partition_type |
314 | - out, err = run_sfdisk_commands( |
315 | - sfdisk_cmd, heads, sectors, cylinders_arg, device) |
316 | - stdout.append(out) |
317 | - stderr.append(err) |
318 | - return "\n".join(stdout), "\n".join(stderr) |
319 | - |
320 | - |
321 | -if __name__ == "__main__": |
322 | - board, device, fat_size, heads, sectors, cylinders_arg = sys.argv[1:] |
323 | - fat_size = int(fat_size) |
324 | - heads = int(heads) |
325 | - sectors = int(sectors) |
326 | - stdout, stderr = create_partitions( |
327 | - board, device, fat_size, heads, sectors, cylinders_arg) |
328 | - print stdout |
329 | - print stderr |
330 | + run_sfdisk_commands(sfdisk_cmd, heads, sectors, cylinders, media.path) |
331 | + |
332 | + # Sync and sleep to wait for the partition to settle. |
333 | + cmd_runner.run(['sync']).wait() |
334 | + # Sleeping just 1 second seems to be enough here, but if we start getting |
335 | + # errors because the disk is not partitioned then we should revisit this. |
336 | + time.sleep(1) |
337 | |
338 | === removed file 'media_create/partition_size.py' |
339 | --- media_create/partition_size.py 2010-12-08 19:54:08 +0000 |
340 | +++ media_create/partition_size.py 1970-01-01 00:00:00 +0000 |
341 | @@ -1,42 +0,0 @@ |
342 | -import sys |
343 | - |
344 | -from parted import ( |
345 | - Device, |
346 | - Disk, |
347 | - ) |
348 | - |
349 | - |
350 | -def calculate_partition_size_and_offset(device): |
351 | - """Return the size and offset of the boot and root partitions. |
352 | - |
353 | - Both the size and offset are in sectors. |
354 | - |
355 | - :param device: A string containing the path to the device. |
356 | - :return: A 4-tuple containing the offset and size of the boot partition |
357 | - followed by the offset and size of the root partition. |
358 | - """ |
359 | - disk = Disk(Device(device)) |
360 | - vfat_partition = None |
361 | - for partition in disk.partitions: |
362 | - if 'boot' in partition.getFlagsAsString(): |
363 | - geometry = partition.geometry |
364 | - vfat_offset = geometry.start * 512 |
365 | - vfat_size = geometry.length * 512 |
366 | - vfat_partition = partition |
367 | - |
368 | - assert vfat_partition is not None, ( |
369 | - "Couldn't find boot partition on %s" % device) |
370 | - linux_partition = vfat_partition.nextPartition() |
371 | - geometry = linux_partition.geometry |
372 | - linux_offset = geometry.start * 512 |
373 | - linux_size = geometry.length * 512 |
374 | - return vfat_size, vfat_offset, linux_size, linux_offset |
375 | - |
376 | - |
377 | -if __name__ == "__main__": |
378 | - vsize, voffset, rsize, roffset = calculate_partition_size_and_offset( |
379 | - sys.argv[1]) |
380 | - # These values need to be assigned to shell variables in our script, so we |
381 | - # make its job easier by printing something it can pass on to eval. |
382 | - print "VFATSIZE=%s VFATOFFSET=%s ROOTSIZE=%s ROOTOFFSET=%s" % ( |
383 | - vsize, voffset, rsize, roffset) |
384 | |
385 | === added file 'media_create/partitions.py' |
386 | --- media_create/partitions.py 1970-01-01 00:00:00 +0000 |
387 | +++ media_create/partitions.py 2010-12-14 13:31:33 +0000 |
388 | @@ -0,0 +1,193 @@ |
389 | +import subprocess |
390 | +import sys |
391 | + |
392 | +import dbus |
393 | +from parted import ( |
394 | + Device, |
395 | + Disk, |
396 | + ) |
397 | + |
398 | +from media_create import cmd_runner |
399 | +from media_create.create_partitions import create_partitions |
400 | + |
401 | + |
402 | +HEADS = 255 |
403 | +SECTORS = 63 |
404 | +SECTOR_SIZE = 512 # bytes |
405 | +CYLINDER_SIZE = HEADS * SECTORS * SECTOR_SIZE |
406 | + |
407 | + |
408 | +# I wonder if it'd make sense to convert this into a small shim which calls |
409 | +# the appropriate function for the given type of device? I think it's still |
410 | +# small enough that there's not much benefit in doing that, but if it grows we |
411 | +# might want to do it. |
412 | +def setup_partitions(board, media, fat_size, image_size, |
413 | + should_create_partitions): |
414 | + """Make sure the given device is partitioned to boot the given board. |
415 | + |
416 | + :param board: The board's name, as a string. |
417 | + :param media: The Media we should partition. |
418 | + :param fat_size: The FAT size (16 or 32) for the boot partition. |
419 | + :param image_size: The size of the image file, in case we're setting up a |
420 | + QEMU image. |
421 | + :param should_create_partitions: A string with values "yes" or "no", |
422 | + specifying whether or not we should erase existing partitions and |
423 | + create new ones. |
424 | + """ |
425 | + cylinders = None |
426 | + if not media.is_block_device: |
427 | + image_size_in_bytes = convert_size_to_bytes(image_size) |
428 | + cylinders = image_size_in_bytes / CYLINDER_SIZE |
429 | + image_size_in_bytes = cylinders * CYLINDER_SIZE |
430 | + proc = cmd_runner.run( |
431 | + ['qemu-img', 'create', '-f', 'raw', media.path, image_size], |
432 | + stdout=open('/dev/null', 'w')) |
433 | + proc.wait() |
434 | + |
435 | + if should_create_partitions == "yes": |
436 | + create_partitions(board, media, fat_size, HEADS, SECTORS, cylinders) |
437 | + |
438 | + if media.is_block_device: |
439 | + return get_boot_and_root_partitions_for_media(media) |
440 | + else: |
441 | + return get_boot_and_root_loopback_devices(media.path) |
442 | + |
443 | + |
444 | +def get_boot_and_root_loopback_devices(image_file): |
445 | + """Return the boot and root loopback devices for the given image file. |
446 | + |
447 | + Register the loopback devices as well. |
448 | + """ |
449 | + vfat_size, vfat_offset, linux_size, linux_offset = ( |
450 | + calculate_partition_size_and_offset(image_file)) |
451 | + proc = cmd_runner.run( |
452 | + ['losetup', '-f', '--show', image_file, '--offset', |
453 | + str(vfat_offset), '--sizelimit', str(vfat_size)], |
454 | + stdout=subprocess.PIPE, as_root=True) |
455 | + boot_device, _ = proc.communicate() |
456 | + proc = cmd_runner.run( |
457 | + ['losetup', '-f', '--show', image_file, '--offset', |
458 | + str(linux_offset), '--sizelimit', str(linux_size)], |
459 | + stdout=subprocess.PIPE, as_root=True) |
460 | + root_device, _ = proc.communicate() |
461 | + |
462 | + return boot_device, root_device |
463 | + |
464 | + |
465 | +def calculate_partition_size_and_offset(image_file): |
466 | + """Return the size and offset of the boot and root partitions. |
467 | + |
468 | + Both the size and offset are in sectors. |
469 | + |
470 | + :param image_file: A string containing the path to the image_file. |
471 | + :return: A 4-tuple containing the offset and size of the boot partition |
472 | + followed by the offset and size of the root partition. |
473 | + """ |
474 | + # Here we can use parted.Device to read the partitions because we're |
475 | + # reading from a regular file rather than a block device. If it was a |
476 | + # block device we'd need root rights. |
477 | + disk = Disk(Device(image_file)) |
478 | + vfat_partition = None |
479 | + for partition in disk.partitions: |
480 | + if 'boot' in partition.getFlagsAsString(): |
481 | + geometry = partition.geometry |
482 | + vfat_offset = geometry.start * 512 |
483 | + vfat_size = geometry.length * 512 |
484 | + vfat_partition = partition |
485 | + |
486 | + assert vfat_partition is not None, ( |
487 | + "Couldn't find boot partition on %s" % image_file) |
488 | + linux_partition = vfat_partition.nextPartition() |
489 | + geometry = linux_partition.geometry |
490 | + linux_offset = geometry.start * 512 |
491 | + linux_size = geometry.length * 512 |
492 | + return vfat_size, vfat_offset, linux_size, linux_offset |
493 | + |
494 | + |
495 | +def get_boot_and_root_partitions_for_media(media): |
496 | + """Return the device files for the boot and root partitions of media. |
497 | + |
498 | + If the given media has 2 partitions, the first is boot and the second is |
499 | + root. If there are 3 partitions, the second is boot and third is root. |
500 | + |
501 | + If there are any other number of partitions, ValueError is raised. |
502 | + |
503 | + This function must only be used for block devices. |
504 | + """ |
505 | + assert media.is_block_device, ( |
506 | + "This function must only be used for block devices") |
507 | + |
508 | + partition_count = _get_partition_count(media) |
509 | + |
510 | + if partition_count == 2: |
511 | + partition_offset = 0 |
512 | + elif partition_count == 3: |
513 | + partition_offset = 1 |
514 | + else: |
515 | + raise ValueError( |
516 | + "Unexpected number of partitions on %s: %d" % ( |
517 | + media.path, partition_count)) |
518 | + |
519 | + boot_partition = "%s%d" % (media.path, 1 + partition_offset) |
520 | + root_partition = "%s%d" % (media.path, 2 + partition_offset) |
521 | + return boot_partition, root_partition |
522 | + |
523 | + |
524 | +def _get_partition_count(media): |
525 | + """Return the number of partitions on the given media.""" |
526 | + # We could do the same easily using python-parted but it requires root |
527 | + # rights to read block devices, so we use UDisks here. |
528 | + bus = dbus.SystemBus() |
529 | + udisks = dbus.Interface( |
530 | + bus.get_object("org.freedesktop.UDisks", "/org/freedesktop/UDisks"), |
531 | + 'org.freedesktop.UDisks') |
532 | + device_path = udisks.get_dbus_method('FindDeviceByDeviceFile')(media.path) |
533 | + device = bus.get_object("org.freedesktop.UDisks", device_path) |
534 | + return device.Get( |
535 | + device_path, 'partition-table-count', |
536 | + dbus_interface='org.freedesktop.DBus.Properties') |
537 | + |
538 | + |
539 | +def convert_size_to_bytes(size): |
540 | + """Convert a size string in Kbytes, Mbytes or Gbytes to bytes.""" |
541 | + unit = size[-1].upper() |
542 | + real_size = int(size[:-1]) |
543 | + if unit == 'K': |
544 | + real_size = real_size * 1024 |
545 | + elif unit == 'M': |
546 | + real_size = real_size * 1024 * 1024 |
547 | + elif unit == 'G': |
548 | + real_size = real_size * 1024 * 1024 * 1024 |
549 | + else: |
550 | + raise ValueError("Unknown size format: %s. Use K[bytes], M[bytes] " |
551 | + "or G[bytes]" % size) |
552 | + |
553 | + # Round the size of the raw disk image up to a multiple of 256K so it is |
554 | + # an exact number of SD card erase blocks in length. Otherwise Linux |
555 | + # under qemu cannot access the last part of the card and is likely to |
556 | + # complain that the last partition on the disk has been truncated. This |
557 | + # doesn't appear to work in all cases, though, as can be seen on |
558 | + # https://bugs.launchpad.net/linux-linaro/+bug/673335. |
559 | + if real_size % (1024 * 256): |
560 | + cylinders = real_size / CYLINDER_SIZE |
561 | + real_size = cylinders * CYLINDER_SIZE |
562 | + real_size = ((((real_size - 1) / (1024 * 256)) + 1) * (1024 * 256)) |
563 | + |
564 | + return real_size |
565 | + |
566 | + |
567 | +class Media(object): |
568 | + """A representation of the media where Linaro will be installed.""" |
569 | + |
570 | + def __init__(self, path): |
571 | + self.path = path |
572 | + self.is_block_device = path.startswith('/dev/') |
573 | + |
574 | + |
575 | +if __name__ == "__main__": |
576 | + board, device, fat_size, image_size, should_create_partitions = ( |
577 | + sys.argv[1:]) |
578 | + fat_size = int(fat_size) |
579 | + boot, root = setup_partitions( |
580 | + board, Media(device), fat_size, image_size, should_create_partitions) |
581 | + print "BOOTFS=%s ROOTFS=%s" % (boot, root) |
582 | |
583 | === modified file 'media_create/populate_boot.py' |
584 | --- media_create/populate_boot.py 2010-12-08 13:09:27 +0000 |
585 | +++ media_create/populate_boot.py 2010-12-14 13:31:33 +0000 |
586 | @@ -16,7 +16,9 @@ |
587 | '-n', name, |
588 | '-d', img_data, |
589 | img] |
590 | - return cmd_runner.run(cmd, as_root=as_root, stdout=stdout) |
591 | + proc = cmd_runner.run(cmd, as_root=as_root, stdout=stdout) |
592 | + proc.wait() |
593 | + return proc.returncode |
594 | |
595 | |
596 | def _get_file_matching(regex): |
597 | @@ -58,24 +60,26 @@ |
598 | |
599 | |
600 | def install_mx51evk_boot_loader(imx_file, boot_device_or_file): |
601 | - cmd_runner.run([ |
602 | + proc = cmd_runner.run([ |
603 | "dd", |
604 | "if=%s" % imx_file, |
605 | "of=%s" % boot_device_or_file, |
606 | "bs=1024", |
607 | "seek=1", |
608 | "conv=notrunc"], as_root=True) |
609 | + proc.wait() |
610 | |
611 | |
612 | def install_omap_boot_loader(mlo_file, boot_disk): |
613 | - cmd_runner.run(["cp", "-v", mlo_file, boot_disk], as_root=True) |
614 | + cmd_runner.run(["cp", "-v", mlo_file, boot_disk], as_root=True).wait() |
615 | # XXX: Is this really needed? |
616 | - cmd_runner.run(["sync"]) |
617 | + cmd_runner.run(["sync"]).wait() |
618 | |
619 | |
620 | def make_boot_ini(boot_script, boot_disk): |
621 | - cmd_runner.run( |
622 | + proc = cmd_runner.run( |
623 | ["cp", "-v", boot_script, "%s/boot.ini" % boot_disk], as_root=True) |
624 | + proc.wait() |
625 | |
626 | |
627 | def populate_boot(board, sub_arch, load_addr, uboot_parts_dir, boot_disk, |
628 | |
629 | === modified file 'media_create/tests/fixtures.py' |
630 | --- media_create/tests/fixtures.py 2010-12-09 19:58:24 +0000 |
631 | +++ media_create/tests/fixtures.py 2010-12-14 13:31:33 +0000 |
632 | @@ -65,12 +65,23 @@ |
633 | |
634 | class MockCmdRunnerPopen(object): |
635 | """A mock for cmd_runner.Popen() which stores the args given to it.""" |
636 | - args = None |
637 | - def __call__(self, args, **kwargs): |
638 | - self.args = args |
639 | + calls = None |
640 | + def __call__(self, cmd, *args, **kwargs): |
641 | + if self.calls is None: |
642 | + self.calls = [] |
643 | + if isinstance(cmd, basestring): |
644 | + all_args = [cmd] |
645 | + else: |
646 | + all_args = cmd |
647 | + all_args.extend(args) |
648 | + self.calls.append(all_args) |
649 | self.returncode = 0 |
650 | return self |
651 | |
652 | + def communicate(self, input=None): |
653 | + self.wait() |
654 | + return '', '' |
655 | + |
656 | def wait(self): |
657 | return self.returncode |
658 | |
659 | |
660 | === modified file 'media_create/tests/test_media_create.py' |
661 | --- media_create/tests/test_media_create.py 2010-12-09 20:00:37 +0000 |
662 | +++ media_create/tests/test_media_create.py 2010-12-14 13:31:33 +0000 |
663 | @@ -4,6 +4,7 @@ |
664 | import string |
665 | import subprocess |
666 | import sys |
667 | +import time |
668 | |
669 | from testtools import TestCase |
670 | |
671 | @@ -12,12 +13,20 @@ |
672 | from media_create import cmd_runner |
673 | from media_create import ensure_command |
674 | from media_create import populate_boot |
675 | +from media_create import partitions |
676 | from media_create.boot_cmd import create_boot_cmd |
677 | from media_create.create_partitions import ( |
678 | create_partitions, |
679 | run_sfdisk_commands, |
680 | ) |
681 | -from media_create.partition_size import calculate_partition_size_and_offset |
682 | +from media_create.partitions import ( |
683 | + calculate_partition_size_and_offset, |
684 | + convert_size_to_bytes, |
685 | + get_boot_and_root_loopback_devices, |
686 | + get_boot_and_root_partitions_for_media, |
687 | + Media, |
688 | + setup_partitions, |
689 | + ) |
690 | from media_create.populate_boot import ( |
691 | make_boot_script, |
692 | make_uImage, |
693 | @@ -133,24 +142,28 @@ |
694 | def test_run(self): |
695 | fixture = MockCmdRunnerPopenFixture() |
696 | self.useFixture(fixture) |
697 | - return_code = cmd_runner.run(['foo', 'bar', 'baz']) |
698 | - self.assertEqual(0, return_code) |
699 | - self.assertEqual(['foo', 'bar', 'baz'], fixture.mock.args) |
700 | + proc = cmd_runner.run(['foo', 'bar', 'baz']) |
701 | + self.assertEqual(0, proc.returncode) |
702 | + self.assertEqual([['foo', 'bar', 'baz']], fixture.mock.calls) |
703 | |
704 | def test_run_as_root(self): |
705 | fixture = MockCmdRunnerPopenFixture() |
706 | self.useFixture(fixture) |
707 | cmd_runner.run(['foo', 'bar'], as_root=True) |
708 | - self.assertEqual(['sudo', 'foo', 'bar'], fixture.mock.args) |
709 | + self.assertEqual([['sudo', 'foo', 'bar']], fixture.mock.calls) |
710 | |
711 | def test_run_succeeds_on_zero_return_code(self): |
712 | - return_code = cmd_runner.run(['true']) |
713 | - self.assertEqual(0, return_code) |
714 | + proc = cmd_runner.run(['true']) |
715 | + # Need to wait() here as we're using the real Popen. |
716 | + proc.wait() |
717 | + self.assertEqual(0, proc.returncode) |
718 | |
719 | def test_run_raises_exception_on_non_zero_return_code(self): |
720 | + def run_and_wait(): |
721 | + proc = cmd_runner.run(['false']) |
722 | + proc.wait() |
723 | self.assertRaises( |
724 | - cmd_runner.SubcommandNonZeroReturnValue, |
725 | - cmd_runner.run, ['false']) |
726 | + cmd_runner.SubcommandNonZeroReturnValue, run_and_wait) |
727 | |
728 | def test_run_must_be_given_list_as_args(self): |
729 | self.assertRaises(AssertionError, cmd_runner.run, 'true') |
730 | @@ -181,7 +194,7 @@ |
731 | 'sudo', 'mkimage', '-A', 'arm', '-O', 'linux', '-T', 'kernel', |
732 | '-C', 'none', '-a', 'load_addr', '-e', 'load_addr', '-n', 'Linux', |
733 | '-d', 'parts_dir/vmlinuz-*-sub_arch', 'boot_disk/uImage'] |
734 | - self.assertEqual(expected, fixture.mock.args) |
735 | + self.assertEqual([expected], fixture.mock.calls) |
736 | |
737 | def test_make_uInitrd(self): |
738 | self._mock_get_file_matching() |
739 | @@ -191,7 +204,7 @@ |
740 | 'sudo', 'mkimage', '-A', 'arm', '-O', 'linux', '-T', 'ramdisk', |
741 | '-C', 'none', '-a', '0', '-e', '0', '-n', 'initramfs', |
742 | '-d', 'parts_dir/initrd.img-*-sub_arch', 'boot_disk/uInitrd'] |
743 | - self.assertEqual(expected, fixture.mock.args) |
744 | + self.assertEqual([expected], fixture.mock.calls) |
745 | |
746 | def test_make_boot_script(self): |
747 | self._mock_get_file_matching() |
748 | @@ -201,7 +214,7 @@ |
749 | 'sudo', 'mkimage', '-A', 'arm', '-O', 'linux', '-T', 'script', |
750 | '-C', 'none', '-a', '0', '-e', '0', '-n', 'boot script', |
751 | '-d', 'tmp_dir/boot.cmd', 'boot_script'] |
752 | - self.assertEqual(expected, fixture.mock.args) |
753 | + self.assertEqual([expected], fixture.mock.calls) |
754 | |
755 | def test_get_file_matching(self): |
756 | prefix = ''.join( |
757 | @@ -245,32 +258,46 @@ |
758 | |
759 | class TestCreatePartitions(TestCaseWithFixtures): |
760 | |
761 | + media = Media('/dev/xdz') |
762 | + |
763 | + def setUp(self): |
764 | + super(TestCreatePartitions, self).setUp() |
765 | + # Stub time.sleep() as create_partitions() use that. |
766 | + self.orig_sleep = time.sleep |
767 | + time.sleep = lambda s: None |
768 | + |
769 | + def tearDown(self): |
770 | + super(TestCreatePartitions, self).tearDown() |
771 | + time.sleep = self.orig_sleep |
772 | + |
773 | def test_create_partitions_for_mx51evk(self): |
774 | # For this board we create a one cylinder partition at the beginning. |
775 | popen_fixture = self.useFixture(MockCmdRunnerPopenFixture()) |
776 | sfdisk_fixture = self.useFixture(MockRunSfdiskCommandsFixture()) |
777 | |
778 | - create_partitions('mx51evk', '/dev/sdz', 32, 255, 63, '') |
779 | + create_partitions('mx51evk', self.media, 32, 255, 63, '') |
780 | |
781 | self.assertEqual( |
782 | - ['sudo', 'parted', '-s', '/dev/sdz', 'mklabel', 'msdos'], |
783 | - popen_fixture.mock.args) |
784 | + [['sudo', 'parted', '-s', self.media.path, 'mklabel', 'msdos'], |
785 | + ['sync']], |
786 | + popen_fixture.mock.calls) |
787 | self.assertEqual( |
788 | - [(',1,0xDA', 255, 63, '', '/dev/sdz'), |
789 | - (',9,0x0C,*\n,,,-', 255, 63, '', '/dev/sdz')], |
790 | + [(',1,0xDA', 255, 63, '', self.media.path), |
791 | + (',9,0x0C,*\n,,,-', 255, 63, '', self.media.path)], |
792 | sfdisk_fixture.mock.calls) |
793 | |
794 | def test_create_partitions_for_beagle(self): |
795 | popen_fixture = self.useFixture(MockCmdRunnerPopenFixture()) |
796 | sfdisk_fixture = self.useFixture(MockRunSfdiskCommandsFixture()) |
797 | |
798 | - create_partitions('beagle', '/dev/sdz', 32, 255, 63, '') |
799 | + create_partitions('beagle', self.media, 32, 255, 63, '') |
800 | |
801 | self.assertEqual( |
802 | - ['sudo', 'parted', '-s', '/dev/sdz', 'mklabel', 'msdos'], |
803 | - popen_fixture.mock.args) |
804 | + [['sudo', 'parted', '-s', self.media.path, 'mklabel', 'msdos'], |
805 | + ['sync']], |
806 | + popen_fixture.mock.calls) |
807 | self.assertEqual( |
808 | - [(',9,0x0C,*\n,,,-', 255, 63, '', '/dev/sdz')], |
809 | + [(',9,0x0C,*\n,,,-', 255, 63, '', self.media.path)], |
810 | sfdisk_fixture.mock.calls) |
811 | |
812 | def test_create_partitions_with_img_file(self): |
813 | @@ -278,11 +305,12 @@ |
814 | sfdisk_fixture = self.useFixture(MockRunSfdiskCommandsFixture()) |
815 | |
816 | tempfile = self.createTempFileAsFixture() |
817 | - create_partitions('beagle', tempfile, 32, 255, 63, '') |
818 | + create_partitions('beagle', Media(tempfile), 32, 255, 63, '') |
819 | |
820 | - # popen() was not called as there's no existing partition table for |
821 | - # us to overwrite on the image file. |
822 | - self.assertEqual(None, popen_fixture.mock.args) |
823 | + # Unlike the test for partitioning of a regular block device, in this |
824 | + # case parted was not called as there's no existing partition table |
825 | + # for us to overwrite on the image file. |
826 | + self.assertEqual([['sync']], popen_fixture.mock.calls) |
827 | |
828 | self.assertEqual( |
829 | [(',9,0x0C,*\n,,,-', 255, 63, '', tempfile)], |
830 | @@ -290,10 +318,13 @@ |
831 | |
832 | def test_run_sfdisk_commands(self): |
833 | tempfile = self.createTempFileAsFixture() |
834 | - cmd_runner.run(['qemu-img', 'create', '-f', 'raw', tempfile, '10M'], |
835 | - stdout=subprocess.PIPE) |
836 | + proc = cmd_runner.run( |
837 | + ['qemu-img', 'create', '-f', 'raw', tempfile, '10M'], |
838 | + stdout=subprocess.PIPE) |
839 | + proc.communicate() |
840 | stdout, stderr = run_sfdisk_commands( |
841 | - ',1,0xDA', 5, 63, '', tempfile, as_root=False) |
842 | + ',1,0xDA', 5, 63, '', tempfile, as_root=False, |
843 | + stderr=subprocess.PIPE) |
844 | self.assertIn('Successfully wrote the new partition table', stdout) |
845 | |
846 | def test_run_sfdisk_commands_raises_on_non_zero_returncode(self): |
847 | @@ -301,21 +332,132 @@ |
848 | self.assertRaises( |
849 | cmd_runner.SubcommandNonZeroReturnValue, |
850 | run_sfdisk_commands, |
851 | - ',1,0xDA', 5, 63, '', tempfile, as_root=False) |
852 | - |
853 | - |
854 | -class TestCalculatePartitionSizeAndOffset(TestCaseWithFixtures): |
855 | - |
856 | - def test_foo(self): |
857 | - tempfile = self.createTempFileAsFixture() |
858 | - cmd_runner.run(['qemu-img', 'create', '-f', 'raw', tempfile, '10M'], |
859 | - stdout=subprocess.PIPE) |
860 | - stdout, stderr = run_sfdisk_commands( |
861 | - ',1,0x0C,*\n,,,-', 5, 63, '', tempfile, as_root=False) |
862 | - self.assertIn('Successfully wrote the new partition table', stdout) |
863 | - |
864 | + ',1,0xDA', 5, 63, '', tempfile, as_root=False, |
865 | + stderr=subprocess.PIPE) |
866 | + |
867 | + |
868 | +class TestPartitionSetup(TestCaseWithFixtures): |
869 | + |
870 | + def setUp(self): |
871 | + super(TestPartitionSetup, self).setUp() |
872 | + # Stub time.sleep() as create_partitions() use that. |
873 | + self.orig_sleep = time.sleep |
874 | + time.sleep = lambda s: None |
875 | + |
876 | + def tearDown(self): |
877 | + super(TestPartitionSetup, self).tearDown() |
878 | + time.sleep = self.orig_sleep |
879 | + |
880 | + def test_convert_size_in_kbytes_to_bytes(self): |
881 | + self.assertEqual(512 * 1024, convert_size_to_bytes('512K')) |
882 | + |
883 | + def test_convert_size_in_mbytes_to_bytes(self): |
884 | + self.assertEqual(100 * 1024**2, convert_size_to_bytes('100M')) |
885 | + |
886 | + def test_convert_size_in_gbytes_to_bytes(self): |
887 | + self.assertEqual(12 * 1024**3, convert_size_to_bytes('12G')) |
888 | + |
889 | + def test_convert_size_in_kbytes_to_bytes_rounds_to_256k_multiple(self): |
890 | + # See comment in convert_size_to_bytes as to why we need to do this. |
891 | + self.assertEqual( |
892 | + 3891 * (1024 * 256), convert_size_to_bytes('1000537K')) |
893 | + |
894 | + def test_calculate_partition_size_and_offset(self): |
895 | + tempfile = self._create_qemu_img_with_partitions(',1,0x0C,*\n,,,-') |
896 | vfat_size, vfat_offset, linux_size, linux_offset = ( |
897 | calculate_partition_size_and_offset(tempfile)) |
898 | self.assertEqual( |
899 | [129024L, 32256L, 10321920L, 161280L], |
900 | [vfat_size, vfat_offset, linux_size, linux_offset]) |
901 | + |
902 | + def test_get_boot_and_root_partitions_for_media_with_2_partitions(self): |
903 | + self.useFixture(MockSomethingFixture( |
904 | + partitions, '_get_partition_count', lambda media: 2)) |
905 | + tempfile = self._create_qemu_img_with_partitions(',1,0x0C,*\n,,,-') |
906 | + media = Media(tempfile) |
907 | + # Pretend the image file is a block device, or else |
908 | + # get_boot_and_root_partitions_for_media will choke. |
909 | + media.is_block_device = True |
910 | + self.assertEqual( |
911 | + ("%s%d" % (tempfile, 1), "%s%d" % (tempfile, 2)), |
912 | + get_boot_and_root_partitions_for_media(media)) |
913 | + |
914 | + def test_get_boot_and_root_partitions_for_media_with_3_partitions(self): |
915 | + self.useFixture(MockSomethingFixture( |
916 | + partitions, '_get_partition_count', lambda media: 3)) |
917 | + tempfile = self._create_qemu_img_with_partitions( |
918 | + ',1,0xDA\n,1,0x0C,*\n,,,-') |
919 | + media = Media(tempfile) |
920 | + # Pretend the image file is a block device, or else |
921 | + # get_boot_and_root_partitions_for_media will choke. |
922 | + media.is_block_device = True |
923 | + self.assertEqual( |
924 | + ("%s%d" % (tempfile, 2), "%s%d" % (tempfile, 3)), |
925 | + get_boot_and_root_partitions_for_media(media)) |
926 | + |
927 | + def _create_qemu_img_with_partitions(self, sfdisk_commands): |
928 | + tempfile = self.createTempFileAsFixture() |
929 | + proc = cmd_runner.run( |
930 | + ['qemu-img', 'create', '-f', 'raw', tempfile, '10M'], |
931 | + stdout=subprocess.PIPE) |
932 | + proc.communicate() |
933 | + stdout, stderr = run_sfdisk_commands( |
934 | + sfdisk_commands, 5, 63, '', tempfile, as_root=False, |
935 | + # Throw away stderr as sfdisk complains a lot when operating on a |
936 | + # qemu image. |
937 | + stderr=subprocess.PIPE) |
938 | + self.assertIn('Successfully wrote the new partition table', stdout) |
939 | + return tempfile |
940 | + |
941 | + def test_get_boot_and_root_loopback_devices(self): |
942 | + tempfile = self._create_qemu_img_with_partitions(',1,0x0C,*\n,,,-') |
943 | + popen_fixture = self.useFixture(MockCmdRunnerPopenFixture()) |
944 | + # We can't test the return value of get_boot_and_root_loopback_devices |
945 | + # because it'd require running losetup as root, so we just make sure |
946 | + # it calls losetup correctly. |
947 | + get_boot_and_root_loopback_devices(tempfile) |
948 | + self.assertEqual( |
949 | + [['sudo', 'losetup', '-f', '--show', tempfile, '--offset', |
950 | + '32256', '--sizelimit', '129024'], |
951 | + ['sudo', 'losetup', '-f', '--show', tempfile, '--offset', |
952 | + '161280', '--sizelimit', '10321920']], |
953 | + popen_fixture.mock.calls) |
954 | + |
955 | + def test_setup_partitions_for_image_file(self): |
956 | + # In practice we could pass an empty image file to setup_partitions, |
957 | + # but here we mock Popen() and thanks to that the image is not setup |
958 | + # (via qemu-img) inside setup_partitions. That's why we pass an |
959 | + # already setup image file. |
960 | + tempfile = self._create_qemu_img_with_partitions(',1,0x0C,*\n,,,-') |
961 | + popen_fixture = self.useFixture(MockCmdRunnerPopenFixture()) |
962 | + setup_partitions('beagle', Media(tempfile), 32, '2G', 'yes') |
963 | + self.assertEqual( |
964 | + # This is the call that would create the image file. |
965 | + [['qemu-img', 'create', '-f', 'raw', tempfile, '2G'], |
966 | + # This call would partition the image file. |
967 | + ['sudo', 'sfdisk', '-D', '-H', '255', '-S', '63', '-C', '261', |
968 | + tempfile], |
969 | + # Make sure changes are written to disk. |
970 | + ['sync'], |
971 | + # Register boot/root loopback devices so that we can just copy |
972 | + # their contents over and have it written to the image file. |
973 | + ['sudo', 'losetup', '-f', '--show', tempfile, '--offset', |
974 | + '32256', '--sizelimit', '129024'], |
975 | + ['sudo', 'losetup', '-f', '--show', tempfile, '--offset', |
976 | + '161280', '--sizelimit', '10321920']], |
977 | + popen_fixture.mock.calls) |
978 | + |
979 | + def test_setup_partitions_for_block_device(self): |
980 | + self.useFixture(MockSomethingFixture( |
981 | + partitions, '_get_partition_count', lambda media: 2)) |
982 | + tempfile = self._create_qemu_img_with_partitions(',1,0x0C,*\n,,,-') |
983 | + media = Media(tempfile) |
984 | + # Pretend our tempfile is a block device. |
985 | + media.is_block_device = True |
986 | + popen_fixture = self.useFixture(MockCmdRunnerPopenFixture()) |
987 | + setup_partitions('beagle', media, 32, '2G', 'yes') |
988 | + self.assertEqual( |
989 | + [['sudo', 'parted', '-s', tempfile, 'mklabel', 'msdos'], |
990 | + ['sudo', 'sfdisk', '-D', '-H', '255', '-S', '63', tempfile], |
991 | + ['sync']], |
992 | + popen_fixture.mock.calls) |
993 | |
994 | === added file 'tests/integration.txt' |
995 | --- tests/integration.txt 1970-01-01 00:00:00 +0000 |
996 | +++ tests/integration.txt 2010-12-14 13:31:33 +0000 |
997 | @@ -0,0 +1,25 @@ |
998 | +A few integration tests we can run while the migration to python is not |
999 | +complete. They probably require root or access to specific block devices so |
1000 | +they are not meant to be automated. |
1001 | + |
1002 | + # This should print nothing to stdout but will create a binary/ dir under |
1003 | + # the current directory. |
1004 | + >>> python -m media_create.unpack_binary_tarball <path-to-binary-tarball> |
1005 | + |
1006 | + # This will remove the binary/ dir created above |
1007 | + >>> python -m media_create.remove_binary_dir |
1008 | + |
1009 | + # Partition (for real!) /dev/sdb for a beagle board and return the devices |
1010 | + # for the boot and root partitions. |
1011 | + >>> python -m media_create.partitions beagle /dev/sdb 32 2G yes |
1012 | + Checking that no-one is using this disk right now |
1013 | + ... |
1014 | + BOOTFS=/dev/sdb1 ROOTFS=/dev/sdb2 |
1015 | + |
1016 | + # Partition /tmp/beagle.img for a beagle board and return the loopback |
1017 | + # devices for the boot and root partitions. |
1018 | + >>> python -m media_create.partitions beagle /tmp/beagle.img 32 2G yes |
1019 | + Warning: /tmp/beagle.img is not a block device |
1020 | + ... |
1021 | + BOOTFS=/dev/loop0 |
1022 | + ROOTFS=/dev/loop1 |
> === modified file 'media_ create/ create_ partitions. py' create_ partitions. py 2010-12-09 19:58:24 +0000 create_ partitions. py 2010-12-13 14:51:10 +0000
> --- media_create/
> +++ media_create/
>
> cmd_runner.run(
> - ['parted', '-s', device, 'mklabel', 'msdos'], as_root=True)
> + ['parted', '-s', media.path, 'mklabel', 'msdos'], as_root=True)
> + # It sems to be necessary to sleep a bit here to avoid a race
> + # condition with the sfdisk commands executed below. Try removing it
> + # and running the integration tests to see how it fails.
> + time.sleep(0.5)
Isn't it possible to wait() for the parted command to finish?
/Martin