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'])
50 stderr = lvr.stderr.read()
51 if 'not found' in stderr:
57 lvr = subprocess.Popen(['lvrename', "xenvg/%s" % (src,), "xenvg/%s" % (dest,)],
58 stderr=subprocess.PIPE,
59 stdout=getOutput()['stdout'])
63 stderr = lvr.stderr.read()
64 if not ('not found in volume group' in stderr):
69 subprocess.Popen(['lvchange', '-ay', "xenvg/%s" % (dest,)],
70 stderr=subprocess.PIPE,
71 stdout=getOutput()['stdout'])
76 print lvr.stderr.read()
81 def lv_random(func, pattern, *args):
83 Run some LVM-related command, optionally with a random string in
86 func takes an LV name plus whatever's in *args and returns the
87 return code of some LVM command, such as lvcreate or lvrename
89 pattern can contain at most one '%s' pattern, which will be
90 replaced by a 6-character random string.
92 If pattern contains a '%s', the script will attempt to re-run
93 itself if the error code indicates that the destination already
96 # Keep trying until it works
98 rand_string = ''.join(random.choice(string.ascii_letters) \
101 name = pattern % rand_string
104 ret = func(name, *args)
107 # 5 is the return code if the destination already exists
108 elif '%s' not in pattern or ret != 5:
109 raise InvirtImageException, 'E: Error running %s with args %s' % (func.__name__, args)
111 def lvcreate_random(pattern, size):
113 Creates an LV, optionally with a random string in the name.
115 Call with a string formatting pattern with a single '%s' to use as
116 a pattern for the name of the new LV.
118 return lv_random(lvcreate, pattern, size)
120 def lvrename_random(src, pattern):
122 Rename an LV to a new name with a random string incorporated.
124 Call with a string formatting pattern with a single '%s' to use as
125 a pattern for the name of the new LV
127 return lv_random(lvrename, pattern, src)
129 def fetch_image(cdrom):
131 Download a cdrom from a URI, shelling out to rsync if appropriate
132 and otherwise trying to use urllib
134 full_uri = os.path.join(cdrom.mirror.uri_prefix, cdrom.uri_suffix)
135 temp_file = tempfile.mkstemp()[1]
137 print >>sys.stderr, "Fetching image %s from %s to %s" % (cdrom.cdrom_id, full_uri, temp_file)
139 if full_uri.startswith('rsync://'):
140 if subprocess.call(['rsync', '--no-motd', '-tLP', full_uri, temp_file],
142 raise InvirtImageException, "E: Unable to download '%s'" % full_uri
144 # I'm not going to look for errors here, because I bet it'll
145 # throw its own exceptions
146 urllib.urlretrieve(full_uri, temp_file)
152 def copy_file(src, dest):
154 Copy a file from one location to another using dd
156 if subprocess.call(['dd', 'if=%s' % src, 'of=%s' % dest, 'bs=1M'],
158 raise InvirtImageException, 'E: Unable to transfer %s into %s' % (src, dest)
160 def load_image(cdrom):
162 Update a cdrom image by downloading the latest version,
163 transferring it into an LV, moving the old LV out of the way and
164 the new LV into place
166 if cdrom.mirror_id is None:
169 temp_file = fetch_image(cdrom)
170 except InvirtImageException, e:
171 print >>sys.stderr, 'ERROR: %s. Skipping.' % e
175 st_size = os.stat(temp_file).st_size
177 print >>sys.stderr, "Failed to fetch %s" % cdrom.cdrom_id
179 cdrom_size = '%sM' % math.ceil((float(st_size) / (1024 * 1024)))
180 new_lv = lvcreate_random('image-new_%s_%%s' % cdrom.cdrom_id, cdrom_size)
181 copy_file(temp_file, '/dev/xenvg/%s' % new_lv)
182 lvrename_random('image_%s' % cdrom.cdrom_id, 'image-old_%s_%%s' % cdrom.cdrom_id)
183 lvrename_random(new_lv, 'image_%s' % cdrom.cdrom_id)
190 Remove stale cdrom images that are no longer in use
192 load_image doesn't attempt to remove the old image because it
193 might still be in use. reap_images attempts to delete any LVs
194 starting with 'image-old_', but ignores errors, in case they're
197 lvm_list = subprocess.Popen(['lvs', '-o', 'lv_name', '--noheadings'],
198 stdout=subprocess.PIPE,
199 stdin=subprocess.PIPE)
202 for lv in map(str.strip, lvm_list.stdout.read().splitlines()):
203 if lv.startswith('image-old_'):
204 subprocess.call(['lvchange', '-a', 'n', '/dev/xenvg/%s' % lv],
206 subprocess.call(['lvchange', '-a', 'n', '/dev/xenvg/%s' % lv],
208 subprocess.call(['lvchange', '-a', 'ey', '/dev/xenvg/%s' % lv],
210 subprocess.call(['lvremove', '--force', '/dev/xenvg/%s' % lv],
217 database.session.begin()
219 usage = """%prog [options] --add [--cdrom] cdrom_id description mirror_id uri_suffix
220 %prog [options] --add --mirror mirror_id uri_prefix
222 %prog [options] --update [short_name1 [short_name2 ...]]
223 %prog [options] --reap"""
225 parser = op.OptionParser(usage=usage)
226 parser.set_defaults(verbosity=0,
229 parser.add_option('-a', '--add', action='store_const',
230 dest='action', const='add',
231 help='Add a new item to the database')
233 parser.add_option('-u', '--update', action='store_const',
234 dest='action', const='update',
235 help='Update all cdrom images in the database with the latest version')
236 parser.add_option('-r', '--reap', action='store_const',
237 dest='action', const='reap',
238 help='Reap stale cdrom images that are no longer in use')
240 a_group = op.OptionGroup(parser, 'Adding new items')
241 a_group.add_option('-c', '--cdrom', action='store_const',
242 dest='item', const='cdrom',
243 help='Add a new cdrom to the database')
244 a_group.add_option('-m', '--mirror', action='store_const',
245 dest='item', const='mirror',
246 help='Add a new mirror to the database')
247 parser.add_option_group(a_group)
249 v_group = op.OptionGroup(parser, "Verbosity levels")
250 v_group.add_option("-q", "--quiet", action='store_const',
251 dest='verbosity', const=0,
252 help='Show no output from commands this script runs (default)')
253 v_group.add_option("-v", "--verbose", action='store_const',
254 dest='verbosity', const=1,
255 help='Show only errors from commands this script runs')
256 v_group.add_option("--noisy", action='store_const',
257 dest='verbosity', const=2,
258 help='Show all output from commands this script runs')
259 parser.add_option_group(v_group)
261 (options, args) = parser.parse_args()
262 verbosity = options.verbosity
263 if options.action is None:
264 print parser.format_help()
265 elif options.action == 'add':
266 if options.item == 'cdrom':
267 attrs = dict(zip(('cdrom_id', 'description', 'mirror_id', 'uri_suffix'),
269 cdrom = database.CDROM(**attrs)
270 database.session.add(cdrom)
271 database.session.commit()
275 elif options.item == 'mirror':
276 attrs = dict(zip(('mirror_id', 'uri_prefix'),
278 mirror = database.Mirror(**attrs)
279 database.session.add(mirror)
280 database.session.commit()
281 elif options.action == 'update':
283 images = [database.CDROM.query.get(arg) for arg in args]
285 images = database.CDROM.query.all()
287 if cdrom is not None:
289 elif options.action == 'reap':
292 if __name__ == '__main__':