34 class RSyncServer (object) : |
34 class RSyncServer (object) : |
35 """ |
35 """ |
36 rsync server-mode execution. |
36 rsync server-mode execution. |
37 """ |
37 """ |
38 |
38 |
39 def _execute (self, options, path) : |
39 def _execute (self, options, srcdst, path) : |
40 """ |
40 """ |
41 Underlying rsync just reads from filesystem. |
41 Underlying rsync just reads from filesystem. |
42 """ |
42 |
|
43 options - list of rsync options |
|
44 srcdst - the (source, dest) pair with None placeholder, as returned by parse_command |
|
45 path - the real path to replace None with |
|
46 """ |
|
47 |
|
48 # one of this will be None |
|
49 src, dst = srcdst |
|
50 |
|
51 # replace None -> path |
|
52 src = src or path |
|
53 dst = dst or path |
|
54 |
|
55 log.debug("%r -> %r", src, dst) |
43 |
56 |
44 # invoke directly, no option-handling, nor stdin/out redirection |
57 # invoke directly, no option-handling, nor stdin/out redirection |
45 invoke.invoke(RSYNC, options + ['.', path], data=False) |
58 invoke.invoke(RSYNC, options + [ src, dst ], data=False) |
46 |
59 |
47 class RSyncFSServer (RSyncServer) : |
60 class RSyncFSServer (RSyncServer) : |
48 """ |
61 """ |
49 Normal filesystem backup. |
62 Normal filesystem backup. |
50 """ |
63 """ |
52 def __init__ (self, path) : |
65 def __init__ (self, path) : |
53 RSyncServer.__init__(self) |
66 RSyncServer.__init__(self) |
54 |
67 |
55 self.path = path |
68 self.path = path |
56 |
69 |
57 def execute (self, options) : |
70 def execute (self, options, srcdst) : |
58 return self._execute(options, self.path) |
71 """ |
|
72 options - list of rsync options |
|
73 srcdst - the (source, dest) pair with None placeholder, as returned by parse_command |
|
74 """ |
|
75 |
|
76 return self._execute(options, srcdst, self.path) |
59 |
77 |
60 class RSyncLVMServer (RSyncServer) : |
78 class RSyncLVMServer (RSyncServer) : |
61 """ |
79 """ |
62 Backup LVM LV by snapshotting + mounting it. |
80 Backup LVM LV by snapshotting + mounting it. |
63 """ |
81 """ |
71 RSyncServer.__init__(self) |
89 RSyncServer.__init__(self) |
72 |
90 |
73 self.volume = volume |
91 self.volume = volume |
74 self.snapshot_opts = opts |
92 self.snapshot_opts = opts |
75 |
93 |
76 def execute (self, options) : |
94 def execute (self, options, srcdst) : |
77 """ |
95 """ |
78 Snapshot, mount, execute |
96 Snapshot, mount, execute |
79 |
97 |
80 options - list of rsync options |
98 options - list of rsync options |
|
99 srcdst - the (source, dest) pair with None placeholder, as returned by parse_command |
81 """ |
100 """ |
82 |
101 |
83 # backup target from LVM command |
102 # backup target from LVM command |
84 lvm = self.volume.lvm |
103 lvm = self.volume.lvm |
85 volume = self.volume |
104 volume = self.volume |
95 with mount(snapshot.dev_path, name_hint=('lvm_' + snapshot.name + '_'), readonly=True) as mountpoint: |
114 with mount(snapshot.dev_path, name_hint=('lvm_' + snapshot.name + '_'), readonly=True) as mountpoint: |
96 # rsync! |
115 # rsync! |
97 log.info("Running rsync: %s", mountpoint) |
116 log.info("Running rsync: %s", mountpoint) |
98 |
117 |
99 # with trailing slash |
118 # with trailing slash |
100 return self._execute(options, mountpoint.path + '/') |
119 return self._execute(options, srcdst, mountpoint.path + '/') |
101 |
120 |
102 # cleanup |
121 # cleanup |
103 # cleanup |
122 # cleanup |
104 |
123 |
105 def parse_command (command_parts, restrict_server=True, restrict_readonly=True) : |
124 def parse_command (command_parts, restrict_server=True, restrict_readonly=True) : |
108 |
127 |
109 command_parts - the command-list sent by rsync |
128 command_parts - the command-list sent by rsync |
110 restrict_server - restrict to server-mode |
129 restrict_server - restrict to server-mode |
111 restrict_readonly - restrict to read/send-mode |
130 restrict_readonly - restrict to read/send-mode |
112 |
131 |
|
132 In server mode, source will always be '.', and dest the source/dest. |
|
133 |
113 Returns: |
134 Returns: |
114 |
135 |
115 (cmd, options, source, dest) |
136 (cmd, options, path, (source, dest)) |
|
137 |
|
138 path -> the real source path |
|
139 (source, dest) -> combination of None for path, and the real source/dest |
|
140 |
116 """ |
141 """ |
117 |
142 |
118 cmd = None |
143 cmd = None |
119 options = [] |
144 options = [] |
120 source = None |
145 source = None |
137 # options |
162 # options |
138 have_server = ('--server' in options) |
163 have_server = ('--server' in options) |
139 have_sender = ('--sender' in options) |
164 have_sender = ('--sender' in options) |
140 |
165 |
141 # verify |
166 # verify |
142 if not have_server : |
167 if restrict_server and not have_server : |
143 raise RSyncCommandFormatError("Missing --server") |
168 raise RSyncCommandFormatError("Missing --server") |
144 |
169 |
145 if restrict_readonly and not have_sender : |
170 if restrict_readonly and not have_sender : |
146 raise RSyncCommandFormatError("Missing --sender for readonly") |
171 raise RSyncCommandFormatError("Missing --sender for readonly") |
147 |
172 |
148 # parse path |
173 if not source : |
|
174 raise RSyncCommandFormatError("Missing source path") |
|
175 |
|
176 if not dest: |
|
177 raise RSyncCommandFormatError("Missing dest path") |
|
178 |
|
179 |
|
180 # parse real source |
149 if have_sender : |
181 if have_sender : |
150 # read |
182 # read; first arg will always be . |
151 # XXX: which way does the dot go? |
|
152 if source != '.' : |
183 if source != '.' : |
153 raise RSyncCommandFormatError("Invalid dest for sender") |
184 raise RSyncCommandFormatError("Invalid dest for sender") |
154 |
185 |
155 path = dest |
186 path = dest |
156 |
187 dest = None |
157 else : |
188 |
|
189 log.debug("using server/sender source path: %s", path) |
|
190 |
|
191 elif have_server : |
158 # write |
192 # write |
159 if source != '.' : |
193 if source != '.' : |
160 raise RSyncCommandFormatError("Invalid source for reciever") |
194 raise RSyncCommandFormatError("Invalid source for reciever") |
161 |
195 |
162 path = dest |
196 path = dest |
163 |
197 dest = None |
164 if not path : |
198 |
165 raise RSyncCommandFormatError("Missing path") |
199 log.debug("using server dest path: %s", path) |
|
200 |
|
201 else : |
|
202 # local src -> dst |
|
203 path = source |
|
204 source = None |
|
205 |
|
206 log.debug("using local src path: %s -> %s", path, dest) |
166 |
207 |
167 # ok |
208 # ok |
168 return cmd, options, source, dest |
209 return cmd, options, path, (source, dest) |
169 |
|
170 |
210 |
171 def parse_source (path, restrict_path=False, lvm_opts={}) : |
211 def parse_source (path, restrict_path=False, lvm_opts={}) : |
172 """ |
212 """ |
173 Figure out source to rsync from, based on pseudo-path given in rsync command. |
213 Figure out source to rsync from, based on pseudo-path given in rsync command. |
174 |
214 |
196 lvm, vg, lv = path.split(':') |
236 lvm, vg, lv = path.split(':') |
197 |
237 |
198 except ValueError, e: |
238 except ValueError, e: |
199 raise RSyncCommandFormatError("Invalid lvm pseudo-path: {error}".format(error=e)) |
239 raise RSyncCommandFormatError("Invalid lvm pseudo-path: {error}".format(error=e)) |
200 |
240 |
201 # XXX: validate |
241 # XXX: validate? |
202 |
|
203 log.info("LVM: %s/%s", vg, lv) |
242 log.info("LVM: %s/%s", vg, lv) |
204 |
243 |
205 # open |
244 # open |
206 lvm = LVM(vg) |
245 lvm = LVM(vg) |
207 volume = lvm.volume(lv) |
246 volume = lvm.volume(lv) |