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
1=== modified file 'utah/iso.py'
2--- utah/iso.py 2012-12-08 02:10:12 +0000
3+++ utah/iso.py 2012-12-12 11:31:23 +0000
4@@ -79,7 +79,7 @@
5 # Ubuntu 12.10 "Quantal Quetzal" - Alpha amd64 (20120820)
6 # i.e. Ubuntu Version (LTS) "Series Codename" - Alpha/Beta/Release
7 # arch (buildnumber)
8- self.media_info = self.dump(self.infofile).communicate()[0]
9+ self.media_info = self.dump(self.infofile)
10 self.arch = self.getarch()
11 self.series = self.getseries()
12 self.buildnumber = self.getbuildnumber()
13@@ -107,8 +107,8 @@
14 self.logger.addHandler(self.filehandler)
15 if config.debuglog is not None:
16 self.logger.setLevel(logging.DEBUG)
17- self.debughandler = (
18- logging.handlers.WatchedFileHandler(config.debuglog))
19+ self.debughandler = (logging.handlers
20+ .WatchedFileHandler(config.debuglog))
21 self.debughandler.setFormatter(file_formatter)
22 self.debughandler.setLevel(logging.DEBUG)
23 self.logger.addHandler(self.debughandler)
24@@ -183,77 +183,88 @@
25 return subprocess.Popen(cmd, stdout=subprocess.PIPE,
26 stderr=subprocess.PIPE).communicate()
27
28- def getrealfile(self, filename, outdir=None):
29- """
30- Return a command to safely extract a file from an ISO.
31+ def getrealfile(self, filename):
32+ """Return a command to safely extract a file from an ISO.
33+
34 Based on unbsdtar-safelink from ubuntu ISO testing.
35+
36+ :params filename: Path to the file in the ISO to be extracted
37+ :type filename: str
38+ :returns: Command that can be passed to a subprocess method
39+ :rtype: list
40+
41 """
42 cmd = ['bsdtar', '-t', '-v', '-f', self.image, filename]
43 self.logger.debug('bsdtar list command: ' + ' '.join(cmd))
44- output = (subprocess.Popen(cmd, stdout=subprocess.PIPE)
45- .communicate()[0].split('\n')[0].split())
46 try:
47- if output[0].startswith('l'):
48- if outdir is None:
49- cmd = ['ln', '-s', output[-1], output[-3]]
50- else:
51- cmd = ['ln', '-s', output[-1],
52- os.path.join(outdir, output[-3])]
53- else:
54- realfile = output[-1]
55- if outdir is None:
56- cmd = ['bsdtar', '-x', '-f', self.image, '-O', realfile]
57- else:
58- cmd = ['bsdtar', '-x', '-f', self.image, '-C', outdir,
59- '-O', realfile]
60- except IndexError:
61- raise UTAHException('Cannot unpack {path} from {image}'
62+ output = subprocess.check_output(cmd)
63+ except subprocess.CalledProcessError:
64+ raise UTAHException('Cannot list {path} in {image}'
65 .format(path=filename, image=self.image))
66+ try:
67+ columns = output.splitlines()[0].split()
68+ realfile = columns[-1]
69+ except (IndexError, subprocess.CalledProcessError):
70+ raise UTAHException('Cannot parse bsdtar list output '
71+ 'for {path} in {image}: {output}'
72+ .format(path=filename, image=self.image,
73+ output=output))
74+ cmd = ['bsdtar', '-x', '-f', self.image, '-O', realfile]
75 self.logger.debug('bsdtar extract command: ' + ' '.join(cmd))
76 return cmd
77
78- def extract(self, filename, outdir=None, outfile=None, **kw):
79- """
80- Return a subprocess to extract a file from an ISO, passing any
81- additional keyword arguments to the subprocess.
82- If outfile is None, extract to standard out.
83- If outdir is present and outfile is None, extract the file to outdir
84- with the same name.
85- If outdir and outfile are both present, extract to outfile in outdir.
86- If the file is a hardlink, extract the actual file.
87- If the file is a symlink, return a command to create the symlink.
88- """
89+ def extract(self, filename, outdir='', outfile=None, **kw):
90+ """Extract file from an ISO.
91+
92+ :param filename: Path to the file in the ISO to be extracted
93+ :type filename: str
94+ :param outdir: Destination directory
95+ :type outdir: str
96+ :param outfile: Destination filename
97+ :type outfile: str
98+ :returns: Path to the extracted file
99+ :rtype: str
100+
101+ .. seealso:: :meth:`getrealfile`
102+
103+ """
104+ cmd = self.getrealfile(filename)
105+ try:
106+ stdout = subprocess.check_output(cmd, **kw)
107+ except subprocess.CalledProcessError:
108+ raise UTAHException('Cannot extract {path} from {image}'
109+ .format(path=filename, image=self.image))
110+
111 if outfile is None:
112- if outdir is None:
113- cmd = self.getrealfile(filename)
114- if cmd[-1] != filename:
115- self.logger.debug(filename + ' appears to be a link to '
116- + cmd[-1])
117- dirname = os.path.dirname(filename)
118- if not os.path.isdir(dirname):
119- os.makedirs(dirname)
120- os.chmod(dirname, 0775)
121- if os.path.isfile(filename):
122- os.chmod(filename, 0775)
123- return subprocess.call(cmd, stdout=open(filename, 'w'),
124- **kw)
125- else:
126- cmd = self.getrealfile(filename, outdir=outdir)
127- if '-O' in cmd:
128- cmd.remove('-O')
129- return subprocess.Popen(cmd, stdout=subprocess.PIPE,
130- stderr=subprocess.PIPE, **kw).communicate()
131- else:
132- cmd = self.getrealfile(filename)
133- return subprocess.call(cmd, stdout=open(outfile, 'w'), **kw)
134+ outfile = os.path.join(outdir, filename)
135+
136+ dirname = os.path.dirname(outfile)
137+ if not os.path.isdir(dirname):
138+ os.makedirs(dirname)
139+ with open(outfile, 'w') as fp:
140+ fp.write(stdout)
141+
142+ return outfile
143
144 def dump(self, filename, **kw):
145- """
146- Return a subprocess dumping a file in an ISO to standard out, passing
147- any additional keyword arguments to the subprocess.
148+ """Extract file contents from an ISO.
149+
150+ :param filename: Name of the file to be extracted
151+ :type filename: str
152+ :returns: Contents of the file
153+ :rtype: str
154+
155+ .. seealso:: :meth:`getrealfile`
156+
157 """
158 cmd = self.getrealfile(filename)
159- return subprocess.Popen(cmd, stdout=subprocess.PIPE, **kw)
160+ try:
161+ stdout = subprocess.check_output(cmd, **kw)
162+ except subprocess.CalledProcessError:
163+ raise UTAHException('Cannot extract {path} from {image}'
164+ .format(path=filename, image=self.image))
165+
166+ return stdout
167
168 def extractall(self):
169 """
170@@ -383,7 +394,7 @@
171 'boot/grub/grub.cfg', 'boot/grub/loopback.cfg']
172 for cfgfile in cfgfiles:
173 if cfgfile in self.listfiles(returnlist=True):
174- stdout = self.dump(cfgfile).communicate()[0]
175+ stdout = self.dump(cfgfile)
176 for line in stdout.splitlines():
177 fragments = line.split()
178 if (len(fragments) >= 2 and
179
180=== modified file 'utah/isotest/iso_static_validation.py'
181--- utah/isotest/iso_static_validation.py 2012-12-10 12:09:29 +0000
182+++ utah/isotest/iso_static_validation.py 2012-12-12 11:31:23 +0000
183@@ -218,15 +218,9 @@
184 self.fail("Failed to fetch manifest file from the server")
185
186 logging.debug('Extracting manifest from the iso')
187- (stdout, stderr) = self.iso.extract('casper/filesystem.manifest',
188- self.temp_dir)
189- logging.debug('Check for error in extracting manifest info from iso')
190- self.assertEqual(stderr, '')
191-
192- manifest_path = os.path.join(self.temp_dir,
193- 'casper/filesystem.manifest')
194+ manifest_path = self.iso.extract('casper/filesystem.manifest',
195+ self.temp_dir)
196 manifest_local = open(manifest_path)
197-
198 for manifest_entry in manifest:
199 logging.debug('Checking manifest are the same in iso and repo')
200 self.assertIn(manifest_entry, manifest_local)
201@@ -235,11 +229,7 @@
202 # the same as that given in distro info
203 def test_build_id(self):
204 logging.debug('Extract distro information of the image from the iso')
205- (stdout, stderr) = self.iso.extract("./.disk/info", self.temp_dir)
206- logging.debug('Checking for error in extracting distro info from iso')
207- self.assertEqual(stderr, '')
208-
209- buildid_path = os.path.join(self.temp_dir, './.disk/info')
210+ buildid_path = self.iso.extract("./.disk/info", self.temp_dir)
211 buildinfo = open(buildid_path)
212
213 try:
214@@ -266,12 +256,7 @@
215 "Skipping images that aren't ubiquity-based.")
216 def test_wubi(self):
217 logging.debug('Extracting wubi from the desktop images')
218- (stdout, stderr) = self.iso.extract('./wubi.exe', self.temp_dir)
219- logging.debug('Check for error in extracting wubi.exe from the iso')
220- self.assertEqual(stderr, '')
221-
222- wubi_path = os.path.join(self.temp_dir, './wubi.exe')
223-
224+ wubi_path = self.iso.extract('./wubi.exe', self.temp_dir)
225 cmd = ["file", wubi_path]
226 output = subprocess.Popen(cmd, stdout=subprocess.PIPE,
227 stderr=subprocess.PIPE)
228@@ -346,40 +331,30 @@
229 @unittest.skipUnless(st_arch != 'powerpc',
230 "vmlinuz is not present only for powerpc images")
231 def test_vmlinuz(self):
232+ logging.debug('Extract kernel image from the iso')
233 path = iso.kernelpath()
234- (stdout, stderr) = self.iso.extract(path, self.temp_dir)
235- logging.debug('Asserting that vmlinuz is present in the iso')
236- self.assertEqual(stderr, '')
237-
238- vmlinuz_path = os.path.join(self.temp_dir, path)
239-
240- cmd = ["file", vmlinuz_path]
241+ vmlinuz_path = self.iso.extract(path, self.temp_dir)
242+ cmd = ['file', vmlinuz_path]
243 output = subprocess.Popen(cmd, stdout=subprocess.PIPE,
244 stderr=subprocess.PIPE)
245 (stdout, stderr) = output.communicate()
246 logging.debug('Check if vmlinuz present in the iso is a Linux kernel')
247 self.assertEqual(stderr, '')
248- self.assertIn("Linux kernel", stdout)
249+ self.assertIn('Linux kernel', stdout)
250
251 # Test if filesystem.squashfs is present and
252 # if that is a squash fs for ubiquity based images
253 @unittest.skipUnless(ubiquity_image, "Skipping for non ubiquity images")
254 def test_filesystem_squashfs(self):
255 logging.debug('Extracting the filesystem.squashfs')
256- (stdout, stderr) = self.iso.extract('casper/filesystem.squashfs',
257- self.temp_dir)
258- logging.debug('Check for error in extracting filesystem.squashfs')
259- self.assertEqual(stderr, '')
260-
261- squashfs_path = os.path.join(self.temp_dir,
262- 'casper/filesystem.squashfs')
263-
264+ squashfs_path = self.iso.extract('casper/filesystem.squashfs',
265+ self.temp_dir)
266 logging.debug('Checking the file is an actual .squashfs')
267 cmd = ["file", squashfs_path]
268 output = subprocess.Popen(cmd, stdout=subprocess.PIPE,
269 stderr=subprocess.PIPE)
270 (stdout, stderr) = output.communicate()
271- logging.debug('Check if signature contains \"Squashfs filesystem\"')
272+ logging.debug('Check if signature contains "Squashfs filesystem"')
273 self.assertEqual(stderr, '')
274 self.assertIn("Squashfs filesystem", stdout)
275

Subscribers

People subscribed via source and target branches