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(['lvrename', 'xenvg', src, dest],
45 stderr=subprocess.PIPE,
46 stdout=getOutput()['stdout'])
50 stderr = lvr.stderr.read()
51 if 'not found in volume group' in stderr:
58 def lv_random(func, pattern, *args):
60 Run some LVM-related command, optionally with a random string in
63 func takes an LV name plus whatever's in *args and returns the
64 return code of some LVM command, such as lvcreate or lvrename
66 pattern can contain at most one '%s' pattern, which will be
67 replaced by a 6-character random string.
69 If pattern contains a '%s', the script will attempt to re-run
70 itself if the error code indicates that the destination already
73 # Keep trying until it works
75 rand_string = ''.join(random.choice(string.ascii_letters) \
78 name = pattern % rand_string
81 ret = func(name, *args)
84 # 5 is the return code if the destination already exists
85 elif '%s' not in pattern or ret != 5:
86 raise InvirtImageException, 'E: Error running %s with args %s' % (func.__name__, args)
88 def lvcreate_random(pattern, size):
90 Creates an LV, optionally with a random string in the name.
92 Call with a string formatting pattern with a single '%s' to use as
93 a pattern for the name of the new LV.
95 return lv_random(lvcreate, pattern, size)
97 def lvrename_random(src, pattern):
99 Rename an LV to a new name with a random string incorporated.
101 Call with a string formatting pattern with a single '%s' to use as
102 a pattern for the name of the new LV
104 return lv_random(lvrename, pattern, src)
106 def fetch_image(cdrom):
108 Download a cdrom from a URI, shelling out to rsync if appropriate
109 and otherwise trying to use urllib
111 full_uri = os.path.join(cdrom.mirror.uri_prefix, cdrom.uri_suffix)
112 temp_file = tempfile.mkstemp()[1]
114 print >>sys.stderr, "Fetching image %s from %s to %s" % (cdrom.cdrom_id, full_uri, temp_file)
116 if full_uri.startswith('rsync://'):
117 if subprocess.call(['rsync', '--no-motd', '-tLP', full_uri, temp_file],
119 raise InvirtImageException, "E: Unable to download '%s'" % full_uri
121 # I'm not going to look for errors here, because I bet it'll
122 # throw its own exceptions
123 urllib.urlretrieve(full_uri, temp_file)
129 def copy_file(src, dest):
131 Copy a file from one location to another using dd
133 if subprocess.call(['dd', 'if=%s' % src, 'of=%s' % dest, 'bs=1M'],
135 raise InvirtImageException, 'E: Unable to transfer %s into %s' % (src, dest)
137 def load_image(cdrom):
139 Update a cdrom image by downloading the latest version,
140 transferring it into an LV, moving the old LV out of the way and
141 the new LV into place
143 if cdrom.mirror_id is None:
145 temp_file = fetch_image(cdrom)
147 st_size = os.stat(temp_file).st_size
149 print >>sys.stderr, "Failed to fetch %s" % cdrom.cdrom_id
151 cdrom_size = '%sM' % math.ceil((float(st_size) / (1024 * 1024)))
152 new_lv = lvcreate_random('image-new_%s_%%s' % cdrom.cdrom_id, cdrom_size)
153 copy_file(temp_file, '/dev/xenvg/%s' % new_lv)
154 lvrename_random('image_%s' % cdrom.cdrom_id, 'image-old_%s_%%s' % cdrom.cdrom_id)
155 lvrename_random(new_lv, 'image_%s' % cdrom.cdrom_id)
162 Remove stale cdrom images that are no longer in use
164 load_image doesn't attempt to remove the old image because it
165 might still be in use. reap_images attempts to delete any LVs
166 starting with 'image-old_', but ignores errors, in case they're
169 lvm_list = subprocess.Popen(['lvs', '-o', 'lv_name', '--noheadings'],
170 stdout=subprocess.PIPE,
171 stdin=subprocess.PIPE)
174 for lv in map(str.strip, lvm_list.stdout.read().splitlines()):
175 if lv.startswith('image-old_'):
176 subprocess.call(['lvchange', '-a', 'n', '/dev/xenvg/%s' % lv],
178 subprocess.call(['lvchange', '-a', 'n', '/dev/xenvg/%s' % lv],
180 subprocess.call(['lvchange', '-a', 'ey', '/dev/xenvg/%s' % lv],
182 subprocess.call(['lvremove', '--force', '/dev/xenvg/%s' % lv],
190 usage = """%prog [options] --add [--cdrom] cdrom_id description mirror_id uri_suffix
191 %prog [options] --add --mirror mirror_id uri_prefix
193 %prog [options] --update [short_name1 [short_name2 ...]]
194 %prog [options] --reap"""
196 parser = op.OptionParser(usage=usage)
197 parser.set_defaults(verbosity=0,
200 parser.add_option('-a', '--add', action='store_const',
201 dest='action', const='add',
202 help='Add a new item to the database')
204 parser.add_option('-u', '--update', action='store_const',
205 dest='action', const='update',
206 help='Update all cdrom images in the database with the latest version')
207 parser.add_option('-r', '--reap', action='store_const',
208 dest='action', const='reap',
209 help='Reap stale cdrom images that are no longer in use')
211 a_group = op.OptionGroup(parser, 'Adding new items')
212 a_group.add_option('-c', '--cdrom', action='store_const',
213 dest='item', const='cdrom',
214 help='Add a new cdrom to the database')
215 a_group.add_option('-m', '--mirror', action='store_const',
216 dest='item', const='mirror',
217 help='Add a new mirror to the database')
218 parser.add_option_group(a_group)
220 v_group = op.OptionGroup(parser, "Verbosity levels")
221 v_group.add_option("-q", "--quiet", action='store_const',
222 dest='verbosity', const=0,
223 help='Show no output from commands this script runs (default)')
224 v_group.add_option("-v", "--verbose", action='store_const',
225 dest='verbosity', const=1,
226 help='Show only errors from commands this script runs')
227 v_group.add_option("--noisy", action='store_const',
228 dest='verbosity', const=2,
229 help='Show all output from commands this script runs')
230 parser.add_option_group(v_group)
232 (options, args) = parser.parse_args()
233 verbosity = options.verbosity
234 if options.action is None:
235 print parser.format_help()
236 elif options.action == 'add':
237 if options.item == 'cdrom':
238 attrs = dict(zip(('cdrom_id', 'description', 'mirror_id', 'uri_suffix'),
240 cdrom = database.CDROM(**attrs)
241 database.session.save(cdrom)
242 database.session.flush()
246 elif options.item == 'mirror':
247 attrs = dict(zip(('mirror_id', 'uri_prefix'),
249 mirror = database.Mirror(**attrs)
250 database.session.save(mirror)
251 database.session.flush()
252 elif options.action == 'update':
254 images = [database.CDROM.query().get(arg) for arg in args]
256 images = database.CDROM.query().all()
258 if cdrom is not None:
260 elif options.action == 'reap':
263 if __name__ == '__main__':