Merge lp:~rvb/maas/lp-1313550 into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 4028
Proposed branch: lp:~rvb/maas/lp-1313550
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 158 lines (+65/-16)
2 files modified
src/provisioningserver/import_images/tests/test_uec2roottar.py (+37/-1)
src/provisioningserver/import_images/uec2roottar.py (+28/-15)
To merge this branch: bzr merge lp:~rvb/maas/lp-1313550
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+262314@code.launchpad.net

Commit message

Fix bug 1313550: use xattrs options if tar supports them.

Description of the change

This is based on Scott's https://code.launchpad.net/~smoser/maas/lp-1313550/+merge/261981. I've only refactored the code a tad and added the required unit tests.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good. The self.patch calls could probably be rewritten without the "magic string", such as:

    self.patch(uec2roottar.check_output)

(but this was recently landed in 1.9, so if there is a chance this will be back-ported to 1.8, let's leave it as-is.)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/provisioningserver/import_images/tests/test_uec2roottar.py'
2--- src/provisioningserver/import_images/tests/test_uec2roottar.py 2015-05-07 18:14:38 +0000
3+++ src/provisioningserver/import_images/tests/test_uec2roottar.py 2015-06-18 11:01:41 +0000
4@@ -123,6 +123,7 @@
5 def test__extracts_image(self):
6 tarball = make_tarball_name()
7 self.patch(uec2roottar, 'check_call')
8+ self.patch(uec2roottar, 'check_output')
9 # Cheat: patch away extraction of the tarball, but pass a temporary
10 # directory with an image already in it. The function will think it
11 # just extracted the image from the tarball.
12@@ -145,6 +146,7 @@
13 def test__ignores_other_files(self):
14 tarball = make_tarball_name()
15 self.patch(uec2roottar, 'check_call')
16+ self.patch(uec2roottar, 'check_output')
17 # Make the function think that it found two files in the tarball: an
18 # image and some other file.
19 image = make_image(self)
20@@ -160,6 +162,7 @@
21 def test__fails_if_no_image_found(self):
22 tarball = make_tarball_name()
23 self.patch(uec2roottar, 'check_call')
24+ self.patch(uec2roottar, 'check_output')
25 empty_dir = self.make_dir()
26 error = self.assertRaises(
27 uec2roottar.ImageFileError,
28@@ -171,6 +174,7 @@
29 def test__fails_if_multiple_images_found(self):
30 tarball = make_tarball_name()
31 self.patch(uec2roottar, 'check_call')
32+ self.patch(uec2roottar, 'check_output')
33 working_dir = self.make_dir()
34 files = sorted(
35 factory.make_file(working_dir, name=make_image_name())
36@@ -278,6 +282,22 @@
37 uec2roottar.check_call, MockAnyCall(['umount', mountpoint]))
38
39
40+class TestTarSupportsXattrOpts(MAASTestCase):
41+ """Tests for `tar_supports_xattr_opts`."""
42+
43+ def test__returns_True_if_help_contains_ref_to_xattr(self):
44+ mock_check_call = self.patch(uec2roottar, 'check_output')
45+ mock_check_call.return_value = 'xattr'
46+ self.assertTrue(uec2roottar.tar_supports_xattr_opts())
47+ self.assertThat(mock_check_call, MockCalledOnceWith(['tar', '--help']))
48+
49+ def test__returns_False_if_help_doesnt_contain_ref_to_xattr(self):
50+ mock_check_call = self.patch(uec2roottar, 'check_output')
51+ mock_check_call.return_value = 'nothing'
52+ self.assertFalse(uec2roottar.tar_supports_xattr_opts())
53+ self.assertThat(mock_check_call, MockCalledOnceWith(['tar', '--help']))
54+
55+
56 class TestExtractImage(MAASTestCase):
57 """Tests for `extract_image`."""
58
59@@ -287,10 +307,11 @@
60 [command] = args
61 return command
62
63- def test__extracts_image(self):
64+ def test__extracts_image_if_tar_supports_xattr(self):
65 image = make_image_name()
66 output = make_tarball_name()
67 self.patch(uec2roottar, 'check_call')
68+ self.patch(uec2roottar, 'tar_supports_xattr_opts').return_value = False
69 uec2roottar.extract_image(image, output)
70 self.assertThat(uec2roottar.check_call.mock_calls, HasLength(3))
71 [mount_call, tar_call, umount_call] = uec2roottar.check_call.mock_calls
72@@ -299,6 +320,20 @@
73 self.assertEqual(['tar', '-C'], tar_command[:2])
74 self.assertEqual('umount', self.extract_command_line(umount_call)[0])
75
76+ def test__extracts_image_if_tar_doesnt_supports_xattr(self):
77+ image = make_image_name()
78+ output = make_tarball_name()
79+ self.patch(uec2roottar, 'check_call')
80+ self.patch(uec2roottar, 'tar_supports_xattr_opts').return_value = True
81+ uec2roottar.extract_image(image, output)
82+ self.assertThat(uec2roottar.check_call.mock_calls, HasLength(3))
83+ [mount_call, tar_call, umount_call] = uec2roottar.check_call.mock_calls
84+ self.assertEqual('mount', self.extract_command_line(mount_call)[0])
85+ tar_command = self.extract_command_line(tar_call)
86+ self.assertEqual(
87+ ['tar', '--xattrs', '--xattrs-include=*', '-C'], tar_command[:4])
88+ self.assertEqual('umount', self.extract_command_line(umount_call)[0])
89+
90
91 class TestSetOwnership(MAASTestCase):
92 """Tests for `set_ownership`."""
93@@ -335,6 +370,7 @@
94 output = os.path.join(self.make_dir(), output_name)
95 args = self.make_args(image=image, output=output)
96 self.patch(uec2roottar, 'check_call')
97+ self.patch(uec2roottar, 'check_output')
98 patch_is_filesystem_file(self, True)
99
100 uec2roottar.main(args)
101
102=== modified file 'src/provisioningserver/import_images/uec2roottar.py'
103--- src/provisioningserver/import_images/uec2roottar.py 2015-05-07 18:14:38 +0000
104+++ src/provisioningserver/import_images/uec2roottar.py 2015-06-18 11:01:41 +0000
105@@ -144,25 +144,38 @@
106 unmount(mountpoint)
107
108
109+def tar_supports_xattr_opts():
110+ """Returns True if the system's tar supports the 'xattrs' options."""
111+ out = check_output(['tar', '--help'])
112+ return b"xattr" in out
113+
114+
115 def extract_image(image, output):
116 """Loop-mount `image`, and tar its contents into `output`."""
117+
118+ xattr_opts = []
119+ if tar_supports_xattr_opts():
120+ # Only add the xattrs options if tar supports it.
121+ # For insance tar on 12.04 does *not* support xattrs.
122+ xattr_opts = ['--xattrs', '--xattrs-include=*']
123 with tempdir() as mountpoint:
124+ cmd = ['tar'] + xattr_opts + [
125+ # Work from mountpoint as the current directory.
126+ '-C', mountpoint,
127+ # Options:
128+ # -c: Create tarfile.
129+ # -p: Preserve permissions.
130+ # -S: Handle sparse files efficiently (images have those).
131+ # -z: Compress using gzip.
132+ # -f: Work on given tar file.
133+ '-cpSzf', output,
134+ '--numeric-owner',
135+ # Tar up the "current directory": the mountpoint.
136+ '.',
137+ ]
138+
139 with loop_mount(image, mountpoint):
140- check_call([
141- 'tar',
142- # Work from mountpoint as the current directory.
143- '-C', mountpoint,
144- # Options:
145- # -c: Create tarfile.
146- # -p: Preserve permissions.
147- # -S: Handle sparse files efficiently (images have those).
148- # -z: Compress using gzip.
149- # -f: Work on given tar file.
150- '-cpSzf', output,
151- '--numeric-owner',
152- # Tar up the "current directory": the mountpoint.
153- '.',
154- ])
155+ check_call(cmd)
156
157
158 def set_ownership(path, user=None):