Merge lp:~mvo/snappy/snappy-lp1449032-poor-mans-rsync-15.04 into lp:~snappy-dev/snappy/15.04-deprecated

Proposed by Michael Vogt
Status: Merged
Approved by: John Lenton
Approved revision: 453
Merged at revision: 444
Proposed branch: lp:~mvo/snappy/snappy-lp1449032-poor-mans-rsync-15.04
Merge into: lp:~snappy-dev/snappy/15.04-deprecated
Diff against target: 311 lines (+194/-7)
8 files modified
helpers/helpers.go (+69/-0)
helpers/helpers_test.go (+102/-0)
partition/bootloader_uboot.go (+1/-4)
partition/partition.go (+3/-0)
progress/progress.go (+6/-2)
progress/progress_test.go (+1/-1)
snappy/systemimage.go (+10/-0)
snappy/systemimage_native.go (+2/-0)
To merge this branch: bzr merge lp:~mvo/snappy/snappy-lp1449032-poor-mans-rsync-15.04
Reviewer Review Type Date Requested Status
John Lenton (community) Approve
Review via email: mp+260556@code.launchpad.net

Commit message

Only update changed files in SyncBootloader().

Description of the change

This branch changes SyncBootloader() to only update files that actually are different instead of just copying everything everytime. It also fixes some visual progress artefacts. Please let me know if you prefer that in a different branch.

To post a comment you must log in.
445. By Michael Vogt

progress/progress.go: fix visual artifacts on update

Revision history for this message
John Lenton (chipaca) wrote :

Alas! Needs fixing. Inline.

review: Needs Fixing
Revision history for this message
John Lenton (chipaca) :
Revision history for this message
John Lenton (chipaca) :
446. By Michael Vogt

address review comments, many thanks to John!

447. By Michael Vogt

helpers/helpers.go: add error checking for os.Walk in RSyncWithDelete

448. By Michael Vogt

snappy/systemimage.go: only call pb.Notify() if there is a ProgressMeter

449. By Michael Vogt

progress/progress.go: do not unset t.pbar in Spin()

450. By Michael Vogt

progress/progress.go: make spinner clear its line

Revision history for this message
John Lenton (chipaca) wrote :

More problems.

Also tests don't actually pass (you've broken a Spin() test?).

451. By Michael Vogt

use SkipDir in RSyncWithDelete

452. By Michael Vogt

progress/progress_test.go: fix test

453. By Michael Vogt

add SyncWithDelete fails test

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks very much, really appreciated, see inline reply.

Revision history for this message
John Lenton (chipaca) :
review: Approve
Revision history for this message
Ricardo Salveti (rsalveti) wrote :

Don't we have auto merge for 15.04? Or is the CI just failing for it?

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'helpers/helpers.go'
2--- helpers/helpers.go 2015-04-29 09:10:08 +0000
3+++ helpers/helpers.go 2015-05-29 13:52:36 +0000
4@@ -23,6 +23,7 @@
5 "encoding/hex"
6 "fmt"
7 "io"
8+ "io/ioutil"
9 "math/rand"
10 "os"
11 "os/exec"
12@@ -317,3 +318,71 @@
13 return syscall.Getuid() == 0 || syscall.Getgid() == 0
14
15 }
16+
17+func makeDirMap(dirName string) (map[string]struct{}, error) {
18+ dir, err := ioutil.ReadDir(dirName)
19+ if err != nil {
20+ return nil, err
21+ }
22+
23+ dirMap := make(map[string]struct{})
24+ for _, dent := range dir {
25+ if dent.IsDir() {
26+ return nil, fmt.Errorf("subdirs are not supported")
27+ }
28+ dirMap[dent.Name()] = struct{}{}
29+ }
30+
31+ return dirMap, nil
32+}
33+
34+// RSyncWithDelete syncs srcDir to destDir
35+func RSyncWithDelete(srcDirName, destDirName string) error {
36+
37+ // first remove everything thats not in srcdir
38+ err := filepath.Walk(destDirName, func(path string, info os.FileInfo, err error) error {
39+ if err != nil {
40+ return err
41+ }
42+
43+ // relative to the root "destDirName"
44+ relPath := path[len(destDirName):]
45+ if !FileExists(filepath.Join(srcDirName, relPath)) {
46+ if err := os.RemoveAll(path); err != nil {
47+ return err
48+ }
49+ if info.IsDir() {
50+ return filepath.SkipDir
51+ }
52+ }
53+ return nil
54+ })
55+ if err != nil {
56+ return err
57+ }
58+
59+ // then copy or update the data from srcdir to destdir
60+ err = filepath.Walk(srcDirName, func(path string, info os.FileInfo, err error) error {
61+ if err != nil {
62+ return err
63+ }
64+
65+ // relative to the root "srcDirName"
66+ relPath := path[len(srcDirName):]
67+ if info.IsDir() {
68+ return os.MkdirAll(filepath.Join(destDirName, relPath), info.Mode())
69+ }
70+ src := path
71+ dst := filepath.Join(destDirName, relPath)
72+ if !FilesAreEqual(src, dst) {
73+ // XXX: on snappy-trunk we can use CopyFile here
74+ output, err := exec.Command("cp", "-va", src, dst).CombinedOutput()
75+ if err != nil {
76+ fmt.Errorf("Failed to copy %s to %s (%s)", src, dst, output)
77+ }
78+ }
79+ return nil
80+ })
81+
82+ return err
83+}
84
85=== modified file 'helpers/helpers_test.go'
86--- helpers/helpers_test.go 2015-04-23 11:12:32 +0000
87+++ helpers/helpers_test.go 2015-05-29 13:52:36 +0000
88@@ -19,6 +19,7 @@
89
90 import (
91 "compress/gzip"
92+ "fmt"
93 "io/ioutil"
94 "math/rand"
95 "os"
96@@ -268,3 +269,104 @@
97 c.Assert(err, IsNil)
98 c.Assert(home, Equals, oldHome)
99 }
100+
101+func makeTestFiles(c *C, srcDir, destDir string) {
102+ // a new file
103+ err := ioutil.WriteFile(filepath.Join(srcDir, "new"), []byte(nil), 0644)
104+ c.Assert(err, IsNil)
105+
106+ // a existing file that needs update
107+ err = ioutil.WriteFile(filepath.Join(destDir, "existing-update"), []byte("old-content"), 0644)
108+ c.Assert(err, IsNil)
109+ err = ioutil.WriteFile(filepath.Join(srcDir, "existing-update"), []byte("some-new-content"), 0644)
110+ c.Assert(err, IsNil)
111+
112+ // existing file that needs no update
113+ err = ioutil.WriteFile(filepath.Join(srcDir, "existing-unchanged"), []byte(nil), 0644)
114+ c.Assert(err, IsNil)
115+ err = exec.Command("cp", "-a", filepath.Join(srcDir, "existing-unchanged"), filepath.Join(destDir, "existing-unchanged")).Run()
116+ c.Assert(err, IsNil)
117+
118+ // a file that needs removal
119+ err = ioutil.WriteFile(filepath.Join(destDir, "to-be-deleted"), []byte(nil), 0644)
120+ c.Assert(err, IsNil)
121+}
122+
123+func compareDirs(c *C, srcDir, destDir string) {
124+ d1, err := exec.Command("ls", "-al", srcDir).CombinedOutput()
125+ c.Assert(err, IsNil)
126+ d2, err := exec.Command("ls", "-al", destDir).CombinedOutput()
127+ c.Assert(err, IsNil)
128+ c.Assert(string(d1), Equals, string(d2))
129+ // ensure content got updated
130+ c1, err := exec.Command("sh", "-c", fmt.Sprintf("find %s -type f |xargs cat", srcDir)).CombinedOutput()
131+ c.Assert(err, IsNil)
132+ c2, err := exec.Command("sh", "-c", fmt.Sprintf("find %s -type f |xargs cat", destDir)).CombinedOutput()
133+ c.Assert(err, IsNil)
134+ c.Assert(string(c1), Equals, string(c2))
135+}
136+
137+func (ts *HTestSuite) TestSyncDirs(c *C) {
138+
139+ for _, l := range [][2]string{
140+ [2]string{"src-short", "dst-loooooooooooong"},
141+ [2]string{"src-loooooooooooong", "dst-short"},
142+ [2]string{"src-eq", "dst-eq"},
143+ } {
144+
145+ // ensure we have src, dest dirs with different length
146+ srcDir := filepath.Join(c.MkDir(), l[0])
147+ err := os.MkdirAll(srcDir, 0755)
148+ c.Assert(err, IsNil)
149+ destDir := filepath.Join(c.MkDir(), l[1])
150+ err = os.MkdirAll(destDir, 0755)
151+ c.Assert(err, IsNil)
152+
153+ // add a src subdir
154+ subdir := filepath.Join(srcDir, "subdir")
155+ err = os.Mkdir(subdir, 0755)
156+ c.Assert(err, IsNil)
157+ makeTestFiles(c, subdir, destDir)
158+
159+ // add a dst subdir that needs to get deleted
160+ subdir2 := filepath.Join(destDir, "to-be-deleted-subdir")
161+ err = os.Mkdir(subdir2, 0755)
162+ subdir3 := filepath.Join(subdir2, "to-be-deleted-sub-subdir")
163+ err = os.Mkdir(subdir3, 0755)
164+
165+ // and a toplevel
166+ makeTestFiles(c, srcDir, destDir)
167+
168+ // do it
169+ err = RSyncWithDelete(srcDir, destDir)
170+ c.Assert(err, IsNil)
171+
172+ // ensure meta-data is identical
173+ compareDirs(c, srcDir, destDir)
174+ compareDirs(c, filepath.Join(srcDir, "subdir"), filepath.Join(destDir, "subdir"))
175+ }
176+}
177+
178+func (ts *HTestSuite) TestSyncDirFails(c *C) {
179+ srcDir := c.MkDir()
180+ err := os.MkdirAll(srcDir, 0755)
181+ c.Assert(err, IsNil)
182+
183+ destDir := c.MkDir()
184+ err = os.MkdirAll(destDir, 0755)
185+ c.Assert(err, IsNil)
186+
187+ err = ioutil.WriteFile(filepath.Join(destDir, "meep"), []byte(nil), 0644)
188+ c.Assert(err, IsNil)
189+
190+ // ensure remove fails
191+ err = os.Chmod(destDir, 0100)
192+ c.Assert(err, IsNil)
193+ // make tempdir cleanup work again
194+ defer os.Chmod(destDir, 0755)
195+
196+ // do it
197+ err = RSyncWithDelete(srcDir, destDir)
198+ c.Check(err, NotNil)
199+ c.Check(err, ErrorMatches, ".*permission denied.*")
200+}
201
202=== modified file 'partition/bootloader_uboot.go'
203--- partition/bootloader_uboot.go 2015-04-30 11:28:39 +0000
204+++ partition/bootloader_uboot.go 2015-05-29 13:52:36 +0000
205@@ -218,10 +218,7 @@
206 srcDir := u.currentBootPath
207 destDir := u.otherBootPath
208
209- // always start from scratch: all files here are owned by us.
210- os.RemoveAll(destDir)
211-
212- return runCommand("/bin/cp", "-a", srcDir, destDir)
213+ return helpers.RSyncWithDelete(srcDir, destDir)
214 }
215
216 func (u *uboot) HandleAssets() (err error) {
217
218=== modified file 'partition/partition.go'
219--- partition/partition.go 2015-03-30 14:56:46 +0000
220+++ partition/partition.go 2015-05-29 13:52:36 +0000
221@@ -811,6 +811,9 @@
222 return err
223 }
224
225+ // XXX: first toggle roofs and then handle assets? that seems
226+ // wrong given that handleAssets may fails and we will
227+ // knowingly boot into a broken system
228 err = p.RunWithOther(RW, func(otherRoot string) (err error) {
229 return bootloader.ToggleRootFS()
230 })
231
232=== modified file 'progress/progress.go'
233--- progress/progress.go 2015-04-21 15:52:02 +0000
234+++ progress/progress.go 2015-05-29 13:52:36 +0000
235@@ -128,8 +128,9 @@
236 // Finished stops displaying the progress
237 func (t *TextProgress) Finished() {
238 if t.pbar != nil {
239- t.pbar.FinishPrint("Done")
240+ t.pbar.Finish()
241 }
242+ fmt.Println("Done")
243 }
244
245 // Write is there so that progress can implment a Writer and can be
246@@ -142,7 +143,10 @@
247 // that have a unknown duration
248 func (t *TextProgress) Spin(msg string) {
249 states := `|/-\`
250- fmt.Printf("\r%s[%c]", msg, states[t.spinStep])
251+
252+ // clear until end of line
253+ clearUntilEOL := "\033[K"
254+ fmt.Printf("\r%s[%c]%s", msg, states[t.spinStep], clearUntilEOL)
255 t.spinStep++
256 if t.spinStep >= len(states) {
257 t.spinStep = 0
258
259=== modified file 'progress/progress_test.go'
260--- progress/progress_test.go 2015-04-22 10:46:52 +0000
261+++ progress/progress_test.go 2015-05-29 13:52:36 +0000
262@@ -58,7 +58,7 @@
263 f.Seek(0, 0)
264 progress, err := ioutil.ReadAll(f)
265 c.Assert(err, IsNil)
266- c.Assert(string(progress), Equals, "\rm[|]\rm[/]\rm[-]\rm[\\]\rm[|]\rm[/]")
267+ c.Assert(string(progress), Equals, "\rm[|]\x1b[K\rm[/]\x1b[K\rm[-]\x1b[K\rm[\\]\x1b[K\rm[|]\x1b[K\rm[/]\x1b[K")
268 }
269
270 func (ts *ProgressTestSuite) testAgreed(answer string, value bool, c *C) {
271
272=== modified file 'snappy/systemimage.go'
273--- snappy/systemimage.go 2015-04-15 20:39:24 +0000
274+++ snappy/systemimage.go 2015-05-29 13:52:36 +0000
275@@ -189,6 +189,9 @@
276
277 // Ensure there is always a kernel + initrd to boot with, even
278 // if the update does not provide new versions.
279+ if pb != nil {
280+ pb.Notify("Syncing boot files")
281+ }
282 err = s.partition.SyncBootloaderFiles()
283 if err != nil {
284 return "", err
285@@ -217,6 +220,13 @@
286 return "", err
287 }
288
289+ // XXX: ToggleNextBoot() calls handleAssets() (but not SyncBootloader
290+ // files :/) - handleAssets() may copy kernel/initramfs to the
291+ // sync mounted /boot/uboot, so its very slow, tell the user
292+ // at least that something is going on
293+ if pb != nil {
294+ pb.Notify("Updating boot files")
295+ }
296 if err = s.partition.ToggleNextBoot(); err != nil {
297 return "", err
298 }
299
300=== modified file 'snappy/systemimage_native.go'
301--- snappy/systemimage_native.go 2015-04-02 23:42:34 +0000
302+++ snappy/systemimage_native.go 2015-05-29 13:52:36 +0000
303@@ -156,6 +156,8 @@
304 pb.Set(genericData.Now)
305 }
306 }
307+ // ugly: avoid Spin() artifacts
308+ pb.Notify("\nApply done")
309
310 if err := scanner.Err(); err != nil {
311 return err

Subscribers

People subscribed via source and target branches