3 from invirt import database
13 class InvirtImageException(Exception):
16 # verbosity = 0 means no output from the actual commands
17 # verbosity = 1 means only errors from the actual commands
18 # verbosity = 2 means all output from the actual commands
24 'stdout': subprocess.PIPE if verbosity < 2 else None,
25 'stderr': subprocess.PIPE if verbosity < 1 else None
28 def lvcreate(name, size):
29 lvc = subprocess.Popen(['lvcreate', '-L', size, '-n', name, 'xenvg'],
30 stderr=subprocess.PIPE,
31 stdout=getOutput()['stdout'])
34 stderr = lvc.stderr.read()
35 if 'already exists in volume group' in stderr:
42 def lvrename(dest, src):
43 lvr = subprocess.Popen(['lvrename', 'xenvg', src, dest],
44 stderr=subprocess.PIPE,
45 stdout=getOutput()['stdout'])
49 stderr = lvr.stderr.read()
50 if 'not found in volume group' in stderr:
57 def lv_random(func, pattern, *args):
59 Run some LVM-related command, optionally with a random string in
62 func takes an LV name plus whatever's in *args and returns the
63 return code of some LVM command, such as lvcreate or lvrename
65 pattern can contain at most one '%s' pattern, which will be
66 replaced by a 6-character random string.
68 If pattern contains a '%s', the script will attempt to re-run
69 itself if the error code indicates that the destination already
72 # Keep trying until it works
74 rand_string = ''.join(random.choice(string.ascii_letters) \
77 name = pattern % rand_string
80 ret = func(name, *args)
83 # 5 is the return code if the destination already exists
84 elif '%s' not in pattern or ret != 5:
85 raise InvirtImageException, 'E: Error running %s with args %s' % (func.__name__, args)
87 def lvcreate_random(pattern, size):
89 Creates an LV, optionally with a random string in the name.
91 Call with a string formatting pattern with a single '%s' to use as
92 a pattern for the name of the new LV.
94 return lv_random(lvcreate, pattern, size)
96 def lvrename_random(src, pattern):
98 Rename an LV to a new name with a random string incorporated.
100 Call with a string formatting pattern with a single '%s' to use as
101 a pattern for the name of the new LV
103 return lv_random(lvrename, pattern, src)
105 def fetch_image(cdrom):
107 Download a cdrom from a URI, shelling out to rsync if appropriate
108 and otherwise trying to use urllib
110 full_uri = os.path.join(cdrom.mirror.uri_prefix, cdrom.uri_suffix)
111 temp_file = tempfile.mkstemp()[1]
113 if full_uri.startswith('rsync://'):
114 if subprocess.call(['rsync', '--no-motd', '-tLP', full_uri, temp_file],
116 raise InvirtImageException, "E: Unable to download '%s'" % full_uri
118 # I'm not going to look for errors here, because I bet it'll
119 # throw its own exceptions
120 urllib.urlretrieve(full_uri, temp_file)
126 def copy_file(src, dest):
128 Copy a file from one location to another using dd
130 if subprocess.call(['dd', 'if=%s' % src, 'of=%s' % dest, 'bs=1M'],
132 raise InvirtImageException, 'E: Unable to transfer %s into %s' % (src, dest)
134 def load_image(cdrom):
136 Update a cdrom image by downloading the latest version,
137 transferring it into an LV, moving the old LV out of the way and
138 the new LV into place
140 if cdrom.mirror_id is None:
142 temp_file = fetch_image(cdrom)
144 cdrom_size = '%sM' % math.ceil((float(os.stat(temp_file).st_size) / (1024 * 1024)))
145 new_lv = lvcreate_random('image-new_%s_%%s' % cdrom.cdrom_id, cdrom_size)
146 copy_file(temp_file, '/dev/xenvg/%s' % new_lv)
147 lvrename_random('image_%s' % cdrom.cdrom_id, 'image-old_%s_%%s' % cdrom.cdrom_id)
148 lvrename_random(new_lv, 'image_%s' % cdrom.cdrom_id)
155 Remove stale cdrom images that are no longer in use
157 load_image doesn't attempt to remove the old image because it
158 might still be in use. reap_images attempts to delete any LVs
159 starting with 'image-old_', but ignores errors, in case they're
162 lvm_list = subprocess.Popen(['lvs', '-o', 'lv_name', '--noheadings'],
163 stdout=subprocess.PIPE,
164 stdin=subprocess.PIPE)
167 for lv in map(str.strip, lvm_list.stdout.read().splitlines()):
168 if lv.startswith('image-old_'):
169 subprocess.call(['lvchange', '-a', 'n', '/dev/xenvg/%s' % lv],
171 subprocess.call(['lvchange', '-a', 'n', '/dev/xenvg/%s' % lv],
173 subprocess.call(['lvchange', '-a', 'ey', '/dev/xenvg/%s' % lv],
175 subprocess.call(['lvremove', '--force', '/dev/xenvg/%s' % lv],
183 usage = """%prog [options] --add [--cdrom] cdrom_id description mirror_id uri_suffix
184 %prog [options] --add --mirror mirror_id uri_prefix
186 %prog [options] --update [short_name1 [short_name2 ...]]
187 %prog [options] --reap"""
189 parser = op.OptionParser(usage=usage)
190 parser.set_defaults(verbosity=0,
193 parser.add_option('-a', '--add', action='store_const',
194 dest='action', const='add',
195 help='Add a new item to the database')
197 parser.add_option('-u', '--update', action='store_const',
198 dest='action', const='update',
199 help='Update all cdrom images in the database with the latest version')
200 parser.add_option('-r', '--reap', action='store_const',
201 dest='action', const='reap',
202 help='Reap stale cdrom images that are no longer in use')
204 a_group = op.OptionGroup(parser, 'Adding new items')
205 a_group.add_option('-c', '--cdrom', action='store_const',
206 dest='item', const='cdrom',
207 help='Add a new cdrom to the database')
208 a_group.add_option('-m', '--mirror', action='store_const',
209 dest='item', const='mirror',
210 help='Add a new mirror to the database')
211 parser.add_option_group(a_group)
213 v_group = op.OptionGroup(parser, "Verbosity levels")
214 v_group.add_option("-q", "--quiet", action='store_const',
215 dest='verbosity', const=0,
216 help='Show no output from commands this script runs (default)')
217 v_group.add_option("-v", "--verbose", action='store_const',
218 dest='verbosity', const=1,
219 help='Show only errors from commands this script runs')
220 v_group.add_option("--noisy", action='store_const',
221 dest='verbosity', const=2,
222 help='Show all output from commands this script runs')
223 parser.add_option_group(v_group)
225 (options, args) = parser.parse_args()
226 verbosity = options.verbosity
227 if options.action is None:
228 print parser.format_help()
229 elif options.action == 'add':
230 if options.item == 'cdrom':
231 attrs = dict(zip(('cdrom_id', 'description', 'mirror_id', 'uri_suffix'),
233 cdrom = database.CDROM(**attrs)
234 database.session.save(cdrom)
235 database.session.flush()
239 elif options.item == 'mirror':
240 attrs = dict(zip(('mirror_id', 'uri_prefix'),
242 mirror = database.Mirror(**attrs)
243 database.session.save(mirror)
244 database.session.flush()
245 elif options.action == 'update':
247 images = [database.CDROM.query().get(arg) for arg in args]
249 images = database.CDROM.query().all()
251 if cdrom is not None:
253 elif options.action == 'reap':
256 if __name__ == '__main__':