Merge lp:~javier.collado/utah/bug1087620 into lp:utah

Proposed by Javier Collado
Status: Merged
Approved by: Max Brustkern
Approved revision: 797
Merged at revision: 788
Proposed branch: lp:~javier.collado/utah/bug1087620
Merge into: lp:utah
Diff against target: 274 lines (+83/-97)
2 files modified
utah/iso.py (+72/-61)
utah/isotest/iso_static_validation.py (+11/-36)
To merge this branch: bzr merge lp:~javier.collado/utah/bug1087620
Reviewer Review Type Date Requested Status
Max Brustkern (community) Approve
Javier Collado (community) Needs Resubmitting
Review via email: mp+138808@code.launchpad.net

Description of the change

This branch applies some refactoring to ISO.{getrealfile,extract} methods.

Both metods were too complicated, so I decided to simplify them:
- `getrealfile` the handling of links didn't seem to be used anywhere.
- `extract` was inconsistent in its return time it returned the output
  `subprocess.Popen` or the one from `subprocess.call`.

After the changes, I've run the validation tests on ISOs for desktop/server for
i386/amd64 and they worked fine.

In addition to this, I've run the `pass.run` runlist, but I wasn't successful
with that because openssh-server wasn't installed correctly, but I'm not sure
if that was because of these changes. Please have a look at them and let me
know what do you think.

To post a comment you must log in.
Revision history for this message
Javier Collado (javier.collado) wrote :

Rejecting this merge proposal for the reasons explained in:
https://bugs.launchpad.net/utah/+bug/1087620/comments/3

review: Disapprove
Revision history for this message
Javier Collado (javier.collado) wrote :

I've tested again this branch and this time I haven't found any problem like
the last time.

ISO validation works for all images I've tested (server, desktop, i386, amd64)
and the `pass.run` runlist works fine for desktop i386, so this doesn't seem ot
break something that depends on `dump` or `extractall`.

The reason I'm not happy with the changes is because the extract `getrealfile`
method doesn't reproduce symbolic links as the old version used to do. Anyway,
I find the implementation is clearer now and it can be refactored later to be
more efficient with regard to hard/symbolic links.

review: Needs Resubmitting
lp:~javier.collado/utah/bug1087620 updated
785. By Javier Collado

Merged changes to fix duplicated test cases docstrings

Source branch: lp:~joetalbott/utah/fix_duplicate_docstrings

Revision history for this message
Max Brustkern (nuclearbob) wrote :

I don't think the lack of reproduction of symbolic links is an issue for our current purposes. I mainly put that in before to try to save space.

The previous validation looked for anything in stderr when extracting the files. It looks like we're discarding that now. Should we be looking for errors, in stderr or in the return code of the extraction process? I think it might be nice in general to raise an exception if a file fails to extract.

At some point, a similar overhaul of the dump function (to return the contents of the file rather than a subprocess) would probably be nice as well, but that's not urgent.

review: Needs Information
lp:~javier.collado/utah/bug1087620 updated
786. By Max Brustkern

Added baremetal fixes

787. By Max Brustkern

Updating to version 0.6

Revision history for this message
Javier Collado (javier.collado) wrote :

I've replaced calls to `subprocess.Popen` with calls to
`subprocess.check_output` so that `bsdtar` return code is checked properly. The
`subprocess.CalledProcessError` exception will be caught an re-raised with a
friendlier error message.

Aside from that, I've also refactored `ISO.dump` to return just the file
contents (or raise an exception if the file cannot be extracted).

review: Needs Resubmitting
lp:~javier.collado/utah/bug1087620 updated
790. By Javier Collado

Added check to make sure that bsdtar list command returned a success code

`subprocess.check_output` is used instead of `subprocess.Popen`

791. By Javier Collado

Added check to make sure that bsdtar extract command returned a success code

`subprocess.check_output` is used instead of `subprocess.Popen`

792. By Javier Collado

Refactored `ISO.dump` to check bsdtar return code and return just output file

793. By Javier Collado

Use "extract" in the error message for the bsdtar extract command

794. By Javier Collado

Refactored `ISO.getrealfile` method

bsdtar error and output parsing error are handled in different try/except
clauses to provide better information in the log (parsing error message
contains the otuput from bsdtar)

795. By Javier Collado

Updated `ISO.dump` docstring

796. By Javier Collado

Added seealso pointer in docstring

797. By Javier Collado

Added keyword arguments to subprocess.check_output call

Revision history for this message
Javier Collado (javier.collado) wrote :

Since there were a couple of merge conflicts in the diff, I've rebased my
changes and retested them.

Everything worked fine.

Revision history for this message
Max Brustkern (nuclearbob) wrote :

Looks good to me.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'utah/iso.py'
--- utah/iso.py 2012-12-08 02:10:12 +0000
+++ utah/iso.py 2012-12-12 11:31:23 +0000
@@ -79,7 +79,7 @@
79 # Ubuntu 12.10 "Quantal Quetzal" - Alpha amd64 (20120820)79 # Ubuntu 12.10 "Quantal Quetzal" - Alpha amd64 (20120820)
80 # i.e. Ubuntu Version (LTS) "Series Codename" - Alpha/Beta/Release80 # i.e. Ubuntu Version (LTS) "Series Codename" - Alpha/Beta/Release
81 # arch (buildnumber)81 # arch (buildnumber)
82 self.media_info = self.dump(self.infofile).communicate()[0]82 self.media_info = self.dump(self.infofile)
83 self.arch = self.getarch()83 self.arch = self.getarch()
84 self.series = self.getseries()84 self.series = self.getseries()
85 self.buildnumber = self.getbuildnumber()85 self.buildnumber = self.getbuildnumber()
@@ -107,8 +107,8 @@
107 self.logger.addHandler(self.filehandler)107 self.logger.addHandler(self.filehandler)
108 if config.debuglog is not None:108 if config.debuglog is not None:
109 self.logger.setLevel(logging.DEBUG)109 self.logger.setLevel(logging.DEBUG)
110 self.debughandler = (110 self.debughandler = (logging.handlers
111 logging.handlers.WatchedFileHandler(config.debuglog))111 .WatchedFileHandler(config.debuglog))
112 self.debughandler.setFormatter(file_formatter)112 self.debughandler.setFormatter(file_formatter)
113 self.debughandler.setLevel(logging.DEBUG)113 self.debughandler.setLevel(logging.DEBUG)
114 self.logger.addHandler(self.debughandler)114 self.logger.addHandler(self.debughandler)
@@ -183,77 +183,88 @@
183 return subprocess.Popen(cmd, stdout=subprocess.PIPE,183 return subprocess.Popen(cmd, stdout=subprocess.PIPE,
184 stderr=subprocess.PIPE).communicate()184 stderr=subprocess.PIPE).communicate()
185185
186 def getrealfile(self, filename, outdir=None):186 def getrealfile(self, filename):
187 """187 """Return a command to safely extract a file from an ISO.
188 Return a command to safely extract a file from an ISO.188
189 Based on unbsdtar-safelink from ubuntu ISO testing.189 Based on unbsdtar-safelink from ubuntu ISO testing.
190
191 :params filename: Path to the file in the ISO to be extracted
192 :type filename: str
193 :returns: Command that can be passed to a subprocess method
194 :rtype: list
195
190 """196 """
191 cmd = ['bsdtar', '-t', '-v', '-f', self.image, filename]197 cmd = ['bsdtar', '-t', '-v', '-f', self.image, filename]
192 self.logger.debug('bsdtar list command: ' + ' '.join(cmd))198 self.logger.debug('bsdtar list command: ' + ' '.join(cmd))
193 output = (subprocess.Popen(cmd, stdout=subprocess.PIPE)
194 .communicate()[0].split('\n')[0].split())
195 try:199 try:
196 if output[0].startswith('l'):200 output = subprocess.check_output(cmd)
197 if outdir is None:201 except subprocess.CalledProcessError:
198 cmd = ['ln', '-s', output[-1], output[-3]]202 raise UTAHException('Cannot list {path} in {image}'
199 else:
200 cmd = ['ln', '-s', output[-1],
201 os.path.join(outdir, output[-3])]
202 else:
203 realfile = output[-1]
204 if outdir is None:
205 cmd = ['bsdtar', '-x', '-f', self.image, '-O', realfile]
206 else:
207 cmd = ['bsdtar', '-x', '-f', self.image, '-C', outdir,
208 '-O', realfile]
209 except IndexError:
210 raise UTAHException('Cannot unpack {path} from {image}'
211 .format(path=filename, image=self.image))203 .format(path=filename, image=self.image))
204 try:
205 columns = output.splitlines()[0].split()
206 realfile = columns[-1]
207 except (IndexError, subprocess.CalledProcessError):
208 raise UTAHException('Cannot parse bsdtar list output '
209 'for {path} in {image}: {output}'
210 .format(path=filename, image=self.image,
211 output=output))
212 cmd = ['bsdtar', '-x', '-f', self.image, '-O', realfile]
212 self.logger.debug('bsdtar extract command: ' + ' '.join(cmd))213 self.logger.debug('bsdtar extract command: ' + ' '.join(cmd))
213 return cmd214 return cmd
214215
215 def extract(self, filename, outdir=None, outfile=None, **kw):216 def extract(self, filename, outdir='', outfile=None, **kw):
216 """217 """Extract file from an ISO.
217 Return a subprocess to extract a file from an ISO, passing any218
218 additional keyword arguments to the subprocess.219 :param filename: Path to the file in the ISO to be extracted
219 If outfile is None, extract to standard out.220 :type filename: str
220 If outdir is present and outfile is None, extract the file to outdir221 :param outdir: Destination directory
221 with the same name.222 :type outdir: str
222 If outdir and outfile are both present, extract to outfile in outdir.223 :param outfile: Destination filename
223 If the file is a hardlink, extract the actual file.224 :type outfile: str
224 If the file is a symlink, return a command to create the symlink.225 :returns: Path to the extracted file
225 """226 :rtype: str
227
228 .. seealso:: :meth:`getrealfile`
229
230 """
231 cmd = self.getrealfile(filename)
232 try:
233 stdout = subprocess.check_output(cmd, **kw)
234 except subprocess.CalledProcessError:
235 raise UTAHException('Cannot extract {path} from {image}'
236 .format(path=filename, image=self.image))
237
226 if outfile is None:238 if outfile is None:
227 if outdir is None:239 outfile = os.path.join(outdir, filename)
228 cmd = self.getrealfile(filename)240
229 if cmd[-1] != filename:241 dirname = os.path.dirname(outfile)
230 self.logger.debug(filename + ' appears to be a link to '242 if not os.path.isdir(dirname):
231 + cmd[-1])243 os.makedirs(dirname)
232 dirname = os.path.dirname(filename)244 with open(outfile, 'w') as fp:
233 if not os.path.isdir(dirname):245 fp.write(stdout)
234 os.makedirs(dirname)246
235 os.chmod(dirname, 0775)247 return outfile
236 if os.path.isfile(filename):
237 os.chmod(filename, 0775)
238 return subprocess.call(cmd, stdout=open(filename, 'w'),
239 **kw)
240 else:
241 cmd = self.getrealfile(filename, outdir=outdir)
242 if '-O' in cmd:
243 cmd.remove('-O')
244 return subprocess.Popen(cmd, stdout=subprocess.PIPE,
245 stderr=subprocess.PIPE, **kw).communicate()
246 else:
247 cmd = self.getrealfile(filename)
248 return subprocess.call(cmd, stdout=open(outfile, 'w'), **kw)
249248
250 def dump(self, filename, **kw):249 def dump(self, filename, **kw):
251 """250 """Extract file contents from an ISO.
252 Return a subprocess dumping a file in an ISO to standard out, passing251
253 any additional keyword arguments to the subprocess.252 :param filename: Name of the file to be extracted
253 :type filename: str
254 :returns: Contents of the file
255 :rtype: str
256
257 .. seealso:: :meth:`getrealfile`
258
254 """259 """
255 cmd = self.getrealfile(filename)260 cmd = self.getrealfile(filename)
256 return subprocess.Popen(cmd, stdout=subprocess.PIPE, **kw)261 try:
262 stdout = subprocess.check_output(cmd, **kw)
263 except subprocess.CalledProcessError:
264 raise UTAHException('Cannot extract {path} from {image}'
265 .format(path=filename, image=self.image))
266
267 return stdout
257268
258 def extractall(self):269 def extractall(self):
259 """270 """
@@ -383,7 +394,7 @@
383 'boot/grub/grub.cfg', 'boot/grub/loopback.cfg']394 'boot/grub/grub.cfg', 'boot/grub/loopback.cfg']
384 for cfgfile in cfgfiles:395 for cfgfile in cfgfiles:
385 if cfgfile in self.listfiles(returnlist=True):396 if cfgfile in self.listfiles(returnlist=True):
386 stdout = self.dump(cfgfile).communicate()[0]397 stdout = self.dump(cfgfile)
387 for line in stdout.splitlines():398 for line in stdout.splitlines():
388 fragments = line.split()399 fragments = line.split()
389 if (len(fragments) >= 2 and400 if (len(fragments) >= 2 and
390401
=== modified file 'utah/isotest/iso_static_validation.py'
--- utah/isotest/iso_static_validation.py 2012-12-10 12:09:29 +0000
+++ utah/isotest/iso_static_validation.py 2012-12-12 11:31:23 +0000
@@ -218,15 +218,9 @@
218 self.fail("Failed to fetch manifest file from the server")218 self.fail("Failed to fetch manifest file from the server")
219219
220 logging.debug('Extracting manifest from the iso')220 logging.debug('Extracting manifest from the iso')
221 (stdout, stderr) = self.iso.extract('casper/filesystem.manifest',221 manifest_path = self.iso.extract('casper/filesystem.manifest',
222 self.temp_dir)222 self.temp_dir)
223 logging.debug('Check for error in extracting manifest info from iso')
224 self.assertEqual(stderr, '')
225
226 manifest_path = os.path.join(self.temp_dir,
227 'casper/filesystem.manifest')
228 manifest_local = open(manifest_path)223 manifest_local = open(manifest_path)
229
230 for manifest_entry in manifest:224 for manifest_entry in manifest:
231 logging.debug('Checking manifest are the same in iso and repo')225 logging.debug('Checking manifest are the same in iso and repo')
232 self.assertIn(manifest_entry, manifest_local)226 self.assertIn(manifest_entry, manifest_local)
@@ -235,11 +229,7 @@
235 # the same as that given in distro info229 # the same as that given in distro info
236 def test_build_id(self):230 def test_build_id(self):
237 logging.debug('Extract distro information of the image from the iso')231 logging.debug('Extract distro information of the image from the iso')
238 (stdout, stderr) = self.iso.extract("./.disk/info", self.temp_dir)232 buildid_path = self.iso.extract("./.disk/info", self.temp_dir)
239 logging.debug('Checking for error in extracting distro info from iso')
240 self.assertEqual(stderr, '')
241
242 buildid_path = os.path.join(self.temp_dir, './.disk/info')
243 buildinfo = open(buildid_path)233 buildinfo = open(buildid_path)
244234
245 try:235 try:
@@ -266,12 +256,7 @@
266 "Skipping images that aren't ubiquity-based.")256 "Skipping images that aren't ubiquity-based.")
267 def test_wubi(self):257 def test_wubi(self):
268 logging.debug('Extracting wubi from the desktop images')258 logging.debug('Extracting wubi from the desktop images')
269 (stdout, stderr) = self.iso.extract('./wubi.exe', self.temp_dir)259 wubi_path = self.iso.extract('./wubi.exe', self.temp_dir)
270 logging.debug('Check for error in extracting wubi.exe from the iso')
271 self.assertEqual(stderr, '')
272
273 wubi_path = os.path.join(self.temp_dir, './wubi.exe')
274
275 cmd = ["file", wubi_path]260 cmd = ["file", wubi_path]
276 output = subprocess.Popen(cmd, stdout=subprocess.PIPE,261 output = subprocess.Popen(cmd, stdout=subprocess.PIPE,
277 stderr=subprocess.PIPE)262 stderr=subprocess.PIPE)
@@ -346,40 +331,30 @@
346 @unittest.skipUnless(st_arch != 'powerpc',331 @unittest.skipUnless(st_arch != 'powerpc',
347 "vmlinuz is not present only for powerpc images")332 "vmlinuz is not present only for powerpc images")
348 def test_vmlinuz(self):333 def test_vmlinuz(self):
334 logging.debug('Extract kernel image from the iso')
349 path = iso.kernelpath()335 path = iso.kernelpath()
350 (stdout, stderr) = self.iso.extract(path, self.temp_dir)336 vmlinuz_path = self.iso.extract(path, self.temp_dir)
351 logging.debug('Asserting that vmlinuz is present in the iso')337 cmd = ['file', vmlinuz_path]
352 self.assertEqual(stderr, '')
353
354 vmlinuz_path = os.path.join(self.temp_dir, path)
355
356 cmd = ["file", vmlinuz_path]
357 output = subprocess.Popen(cmd, stdout=subprocess.PIPE,338 output = subprocess.Popen(cmd, stdout=subprocess.PIPE,
358 stderr=subprocess.PIPE)339 stderr=subprocess.PIPE)
359 (stdout, stderr) = output.communicate()340 (stdout, stderr) = output.communicate()
360 logging.debug('Check if vmlinuz present in the iso is a Linux kernel')341 logging.debug('Check if vmlinuz present in the iso is a Linux kernel')
361 self.assertEqual(stderr, '')342 self.assertEqual(stderr, '')
362 self.assertIn("Linux kernel", stdout)343 self.assertIn('Linux kernel', stdout)
363344
364 # Test if filesystem.squashfs is present and345 # Test if filesystem.squashfs is present and
365 # if that is a squash fs for ubiquity based images346 # if that is a squash fs for ubiquity based images
366 @unittest.skipUnless(ubiquity_image, "Skipping for non ubiquity images")347 @unittest.skipUnless(ubiquity_image, "Skipping for non ubiquity images")
367 def test_filesystem_squashfs(self):348 def test_filesystem_squashfs(self):
368 logging.debug('Extracting the filesystem.squashfs')349 logging.debug('Extracting the filesystem.squashfs')
369 (stdout, stderr) = self.iso.extract('casper/filesystem.squashfs',350 squashfs_path = self.iso.extract('casper/filesystem.squashfs',
370 self.temp_dir)351 self.temp_dir)
371 logging.debug('Check for error in extracting filesystem.squashfs')
372 self.assertEqual(stderr, '')
373
374 squashfs_path = os.path.join(self.temp_dir,
375 'casper/filesystem.squashfs')
376
377 logging.debug('Checking the file is an actual .squashfs')352 logging.debug('Checking the file is an actual .squashfs')
378 cmd = ["file", squashfs_path]353 cmd = ["file", squashfs_path]
379 output = subprocess.Popen(cmd, stdout=subprocess.PIPE,354 output = subprocess.Popen(cmd, stdout=subprocess.PIPE,
380 stderr=subprocess.PIPE)355 stderr=subprocess.PIPE)
381 (stdout, stderr) = output.communicate()356 (stdout, stderr) = output.communicate()
382 logging.debug('Check if signature contains \"Squashfs filesystem\"')357 logging.debug('Check if signature contains "Squashfs filesystem"')
383 self.assertEqual(stderr, '')358 self.assertEqual(stderr, '')
384 self.assertIn("Squashfs filesystem", stdout)359 self.assertIn("Squashfs filesystem", stdout)
385360

Subscribers

People subscribed via source and target branches