3 from invirt import database
14 class InvirtImageException(Exception):
17 # verbosity = 0 means no output from the actual commands
18 # verbosity = 1 means only errors from the actual commands
19 # verbosity = 2 means all output from the actual commands
25 'stdout': subprocess.PIPE if verbosity < 2 else None,
26 'stderr': subprocess.PIPE if verbosity < 1 else None
29 def lvcreate(name, size):
30 lvc = subprocess.Popen(['lvcreate', '-L', size, '-n', name, 'xenvg'],
31 stderr=subprocess.PIPE,
32 stdout=getOutput()['stdout'])
35 stderr = lvc.stderr.read()
36 if 'already exists in volume group' in stderr:
43 def lvrename(dest, src):
44 lvr = subprocess.Popen(['lvchange', '-an', "xenvg/%s" % (src,)],
45 stderr=subprocess.PIPE,
46 stdout=getOutput()['stdout'])
51 print lvr.stderr.read()
54 lvr = subprocess.Popen(['lvrename', "xenvg/%s" % (src,), "xenvg/%s" % (dest,)],
55 stderr=subprocess.PIPE,
56 stdout=getOutput()['stdout'])
60 stderr = lvr.stderr.read()
61 if not ('not found in volume group' in stderr):
66 subprocess.Popen(['lvchange', '-ay', "xenvg/%s" % (dest,)],
67 stderr=subprocess.PIPE,
68 stdout=getOutput()['stdout'])
73 print lvr.stderr.read()
78 def lv_random(func, pattern, *args):
80 Run some LVM-related command, optionally with a random string in
83 func takes an LV name plus whatever's in *args and returns the
84 return code of some LVM command, such as lvcreate or lvrename
86 pattern can contain at most one '%s' pattern, which will be
87 replaced by a 6-character random string.
89 If pattern contains a '%s', the script will attempt to re-run
90 itself if the error code indicates that the destination already
93 # Keep trying until it works
95 rand_string = ''.join(random.choice(string.ascii_letters) \
98 name = pattern % rand_string
101 ret = func(name, *args)
104 # 5 is the return code if the destination already exists
105 elif '%s' not in pattern or ret != 5:
106 raise InvirtImageException, 'E: Error running %s with args %s' % (func.__name__, args)
108 def lvcreate_random(pattern, size):
110 Creates an LV, optionally with a random string in the name.
112 Call with a string formatting pattern with a single '%s' to use as
113 a pattern for the name of the new LV.
115 return lv_random(lvcreate, pattern, size)
117 def lvrename_random(src, pattern):
119 Rename an LV to a new name with a random string incorporated.
121 Call with a string formatting pattern with a single '%s' to use as
122 a pattern for the name of the new LV
124 return lv_random(lvrename, pattern, src)
126 def fetch_image(cdrom):
128 Download a cdrom from a URI, shelling out to rsync if appropriate
129 and otherwise trying to use urllib
131 full_uri = os.path.join(cdrom.mirror.uri_prefix, cdrom.uri_suffix)
132 temp_file = tempfile.mkstemp()[1]
134 print >>sys.stderr, "Fetching image %s from %s to %s" % (cdrom.cdrom_id, full_uri, temp_file)
136 if full_uri.startswith('rsync://'):
137 if subprocess.call(['rsync', '--no-motd', '-tLP', full_uri, temp_file],
139 raise InvirtImageException, "E: Unable to download '%s'" % full_uri
141 # I'm not going to look for errors here, because I bet it'll
142 # throw its own exceptions
143 urllib.urlretrieve(full_uri, temp_file)
149 def copy_file(src, dest):
151 Copy a file from one location to another using dd
153 if subprocess.call(['dd', 'if=%s' % src, 'of=%s' % dest, 'bs=1M'],
155 raise InvirtImageException, 'E: Unable to transfer %s into %s' % (src, dest)
157 def load_image(cdrom):
159 Update a cdrom image by downloading the latest version,
160 transferring it into an LV, moving the old LV out of the way and
161 the new LV into place
163 if cdrom.mirror_id is None:
166 temp_file = fetch_image(cdrom)
167 except InvirtImageException, e:
168 print >>sys.stderr, 'ERROR: %s. Skipping.' % e
172 st_size = os.stat(temp_file).st_size
174 print >>sys.stderr, "Failed to fetch %s" % cdrom.cdrom_id
176 cdrom_size = '%sM' % math.ceil((float(st_size) / (1024 * 1024)))
177 new_lv = lvcreate_random('image-new_%s_%%s' % cdrom.cdrom_id, cdrom_size)
178 copy_file(temp_file, '/dev/xenvg/%s' % new_lv)
179 lvrename_random('image_%s' % cdrom.cdrom_id, 'image-old_%s_%%s' % cdrom.cdrom_id)
180 lvrename_random(new_lv, 'image_%s' % cdrom.cdrom_id)
187 Remove stale cdrom images that are no longer in use
189 load_image doesn't attempt to remove the old image because it
190 might still be in use. reap_images attempts to delete any LVs
191 starting with 'image-old_', but ignores errors, in case they're
194 lvm_list = subprocess.Popen(['lvs', '-o', 'lv_name', '--noheadings'],
195 stdout=subprocess.PIPE,
196 stdin=subprocess.PIPE)
199 for lv in map(str.strip, lvm_list.stdout.read().splitlines()):
200 if lv.startswith('image-old_'):
201 subprocess.call(['lvchange', '-a', 'n', '/dev/xenvg/%s' % lv],
203 subprocess.call(['lvchange', '-a', 'n', '/dev/xenvg/%s' % lv],
205 subprocess.call(['lvchange', '-a', 'ey', '/dev/xenvg/%s' % lv],
207 subprocess.call(['lvremove', '--force', '/dev/xenvg/%s' % lv],
214 database.session.begin()
216 usage = """%prog [options] --add [--cdrom] cdrom_id description mirror_id uri_suffix
217 %prog [options] --add --mirror mirror_id uri_prefix
219 %prog [options] --update [short_name1 [short_name2 ...]]
220 %prog [options] --reap"""
222 parser = op.OptionParser(usage=usage)
223 parser.set_defaults(verbosity=0,
226 parser.add_option('-a', '--add', action='store_const',
227 dest='action', const='add',
228 help='Add a new item to the database')
230 parser.add_option('-u', '--update', action='store_const',
231 dest='action', const='update',
232 help='Update all cdrom images in the database with the latest version')
233 parser.add_option('-r', '--reap', action='store_const',
234 dest='action', const='reap',
235 help='Reap stale cdrom images that are no longer in use')
237 a_group = op.OptionGroup(parser, 'Adding new items')
238 a_group.add_option('-c', '--cdrom', action='store_const',
239 dest='item', const='cdrom',
240 help='Add a new cdrom to the database')
241 a_group.add_option('-m', '--mirror', action='store_const',
242 dest='item', const='mirror',
243 help='Add a new mirror to the database')
244 parser.add_option_group(a_group)
246 v_group = op.OptionGroup(parser, "Verbosity levels")
247 v_group.add_option("-q", "--quiet", action='store_const',
248 dest='verbosity', const=0,
249 help='Show no output from commands this script runs (default)')
250 v_group.add_option("-v", "--verbose", action='store_const',
251 dest='verbosity', const=1,
252 help='Show only errors from commands this script runs')
253 v_group.add_option("--noisy", action='store_const',
254 dest='verbosity', const=2,
255 help='Show all output from commands this script runs')
256 parser.add_option_group(v_group)
258 (options, args) = parser.parse_args()
259 verbosity = options.verbosity
260 if options.action is None:
261 print parser.format_help()
262 elif options.action == 'add':
263 if options.item == 'cdrom':
264 attrs = dict(zip(('cdrom_id', 'description', 'mirror_id', 'uri_suffix'),
266 cdrom = database.CDROM(**attrs)
267 database.session.add(cdrom)
268 database.session.commit()
272 elif options.item == 'mirror':
273 attrs = dict(zip(('mirror_id', 'uri_prefix'),
275 mirror = database.Mirror(**attrs)
276 database.session.add(mirror)
277 database.session.commit()
278 elif options.action == 'update':
280 images = [database.CDROM.query().get(arg) for arg in args]
282 images = database.CDROM.query().all()
284 if cdrom is not None:
286 elif options.action == 'reap':
289 if __name__ == '__main__':