0c6b639f8fbbf5016594fd02c91a917d28037416
[snf-image-creator] / image_creator / rsync.py
1 # Copyright 2012 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
11 #   2. Redistributions in binary form must reproduce the above
12 #      copyright notice, this list of conditions and the following
13 #      disclaimer in the documentation and/or other materials
14 #      provided with the distribution.
15 #
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28 #
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
33
34 import subprocess
35 import time
36 import signal
37
38 from image_creator.util import FatalError
39
40
41 class Rsync:
42     """Wrapper class for the rsync command"""
43
44     def __init__(self, src, dest, exclude=[]):
45         """Create an instance by defining a source, a destinationa and a number
46         of exclude patterns.
47         """
48         self.src = src
49         self.dest = dest
50         self.exclude = exclude
51         self.options = ['-v']
52
53     def archive(self):
54         """Enable the archive option"""
55         self.options.append('-a')
56         return self
57
58     def run(self, out):
59         """Run the actual command"""
60         cmd = []
61         cmd.append('rsync')
62         cmd.extend(self.options)
63         for i in self.exclude:
64             cmd.extend(['--exclude', i])
65
66         out.output("Calculating total number of host files ...", False)
67         dry_run = subprocess.Popen(cmd + ['-n', self.src, self.dest],
68                                    shell=False, stdout=subprocess.PIPE,
69                                    bufsize=0)
70         try:
71             total = 0
72             for line in iter(dry_run.stdout.readline, b''):
73                 total += 1
74         finally:
75             dry_run.communicate()
76             if dry_run.returncode != 0:
77                 raise FatalError("rsync failed")
78
79         out.success("%d" % total)
80
81         progress = out.Progress(total, "Copying files into the image ... ")
82         run = subprocess.Popen(cmd + [self.src, self.dest], shell=False,
83                                stdout=subprocess.PIPE, bufsize=0)
84         try:
85             t = time.time()
86             i = 0
87             for line in iter(run.stdout.readline, b''):
88                 i += 1
89                 current = time.time()
90                 if current - t > 0.1:
91                     t = current
92                     progress.goto(i)
93
94             progress.success('done')
95
96         finally:
97             run.poll()
98             if run.returncode is None:
99                 run.send_signal(signal.SIGHUP)
100             run.communicate()
101             if run.returncode != 0:
102                 raise FatalError("rsync failed")
103
104
105 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :