diff --git a/image_creator/dialog_main.py b/image_creator/dialog_main.py index 3082a1c5126cc6ecec21dba014fdaf92219fc022..2ee07374e73a68a791e730cff33aa8d5e08769dc 100644 --- a/image_creator/dialog_main.py +++ b/image_creator/dialog_main.py @@ -43,6 +43,8 @@ import optparse from image_creator import __version__ as version from image_creator.util import FatalError, MD5 +from image_creator.output import Output, CombinedOutput +from image_creator.output.cli import SimpleOutput from image_creator.output.dialog import GaugeOutput, InfoBoxOutput from image_creator.disk import Disk from image_creator.os_type import os_cls @@ -201,33 +203,37 @@ def extract_image(session): "\n".join(overwrite), width=YESNO_WIDTH): continue - out = GaugeOutput(d, "Image Extraction", "Extracting image...") + gauge = GaugeOutput(d, "Image Extraction", "Extracting image...") try: dev = session['device'] - if "checksum" not in session: - size = dev.size - md5 = MD5(out) - session['checksum'] = md5.compute(session['snapshot'], size) - - # Extract image file - dev.out = out - dev.dump(path) - - # Extract metadata file - out.output("Extracting metadata file...") - with open('%s.meta' % path, 'w') as f: - f.write(extract_metadata_string(session)) - out.success('done') - - # Extract md5sum file - out.output("Extracting md5sum file...") - md5str = "%s %s\n" % (session['checksum'], name) - with open('%s.md5sum' % path, 'w') as f: - f.write(md5str) - out.success("done") - + out = dev.out + out.add(gauge) + try: + if "checksum" not in session: + size = dev.size + md5 = MD5(out) + session['checksum'] = md5.compute(session['snapshot'], + size) + + # Extract image file + dev.dump(path) + + # Extract metadata file + out.output("Extracting metadata file...") + with open('%s.meta' % path, 'w') as f: + f.write(extract_metadata_string(session)) + out.success('done') + + # Extract md5sum file + out.output("Extracting md5sum file...") + md5str = "%s %s\n" % (session['checksum'], name) + with open('%s.md5sum' % path, 'w') as f: + f.write(md5str) + out.success("done") + finally: + out.remove(gauge) finally: - out.cleanup() + gauge.cleanup() d.msgbox("Image file `%s' was successfully extracted!" % path, width=MSGBOX_WIDTH) break @@ -237,7 +243,8 @@ def extract_image(session): def upload_image(session): d = session["dialog"] - size = session['device'].size + dev = session['device'] + size = dev.size if "account" not in session: d.msgbox("You need to provide your ~okeanos login username before you " @@ -265,40 +272,47 @@ def upload_image(session): break - out = GaugeOutput(d, "Image Upload", "Uploading...") + gauge = GaugeOutput(d, "Image Upload", "Uploading...") try: - if 'checksum' not in session: - md5 = MD5(out) - session['checksum'] = md5.compute(session['snapshot'], size) - kamaki = Kamaki(session['account'], session['token'], out) + out = dev.out + out.add(gauge) try: - # Upload image file - with open(session['snapshot'], 'rb') as f: - session["upload"] = kamaki.upload(f, size, filename, - "Calculating block hashes", - "Uploading missing blocks") - # Upload metadata file - out.output("Uploading metadata file...") - metastring = extract_metadata_string(session) - kamaki.upload(StringIO.StringIO(metastring), size=len(metastring), - remote_path="%s.meta" % filename) - out.success("done") - - # Upload md5sum file - out.output("Uploading md5sum file...") - md5str = "%s %s\n" % (session['checksum'], filename) - kamaki.upload(StringIO.StringIO(md5str), size=len(md5str), - remote_path="%s.md5sum" % filename) - out.success("done") - - except ClientError as e: - d.msgbox("Error in pithos+ client: %s" % e.message, - title="Pithos+ Client Error", width=MSGBOX_WIDTH) - if 'upload' in session: - del session['upload'] - return False + if 'checksum' not in session: + md5 = MD5(out) + session['checksum'] = md5.compute(session['snapshot'], size) + + kamaki = Kamaki(session['account'], session['token'], out) + try: + # Upload image file + with open(session['snapshot'], 'rb') as f: + session["upload"] = kamaki.upload(f, size, filename, + "Calculating block hashes", + "Uploading missing blocks") + # Upload metadata file + out.output("Uploading metadata file...") + metastring = extract_metadata_string(session) + kamaki.upload(StringIO.StringIO(metastring), + size=len(metastring), + remote_path="%s.meta" % filename) + out.success("done") + + # Upload md5sum file + out.output("Uploading md5sum file...") + md5str = "%s %s\n" % (session['checksum'], filename) + kamaki.upload(StringIO.StringIO(md5str), size=len(md5str), + remote_path="%s.md5sum" % filename) + out.success("done") + + except ClientError as e: + d.msgbox("Error in pithos+ client: %s" % e.message, + title="Pithos+ Client Error", width=MSGBOX_WIDTH) + if 'upload' in session: + del session['upload'] + return False + finally: + out.remove(gauge) finally: - out.cleanup() + gauge.cleanup() d.msgbox("Image file `%s' was successfully uploaded to pithos+" % filename, width=MSGBOX_WIDTH) @@ -308,6 +322,7 @@ def upload_image(session): def register_image(session): d = session["dialog"] + dev = session['device'] if "account" not in session: d.msgbox("You need to provide your ~okeanos login username before you " @@ -344,18 +359,23 @@ def register_image(session): for key in session['task_metadata']: metadata[key] = 'yes' - out = GaugeOutput(d, "Image Registration", "Registrating image...") + gauge = GaugeOutput(d, "Image Registration", "Registrating image...") try: - out.output("Registring image to cyclades...") + out = dev.out + out.add(gauge) try: - kamaki = Kamaki(session['account'], session['token'], out) - kamaki.register(name, session['upload'], metadata) - out.success('done') - except ClientError as e: - d.msgbox("Error in pithos+ client: %s" % e.message) - return False + out.output("Registring image to cyclades...") + try: + kamaki = Kamaki(session['account'], session['token'], out) + kamaki.register(name, session['upload'], metadata) + out.success('done') + except ClientError as e: + d.msgbox("Error in pithos+ client: %s" % e.message) + return False + finally: + out.remove(gauge) finally: - out.cleanup() + gauge.cleanup() d.msgbox("Image `%s' was successfully registered to cyclades as `%s'" % (session['upload'], name), width=MSGBOX_WIDTH) @@ -657,30 +677,31 @@ def sysprep(session): else: image_os.disable_sysprep(syspreps[i]) - out = InfoBoxOutput(d, "Image Configuration") + infobox = InfoBoxOutput(d, "Image Configuration") try: dev = session['device'] - dev.out = out - dev.mount(readonly=False) + dev.out.add(infobox) try: - # The checksum is invalid. We have mounted the image rw - if 'checksum' in session: - del session['checksum'] - - # Monitor the metadata changes during syspreps - with metadata_monitor(session, image_os.meta): - image_os.out = out - image_os.do_sysprep() - image_os.out.finalize() - - # Disable syspreps that have ran - for sysprep in session['exec_syspreps']: - image_os.disable_sysprep(sysprep) - + dev.mount(readonly=False) + try: + # The checksum is invalid. We have mounted the image rw + if 'checksum' in session: + del session['checksum'] + + # Monitor the metadata changes during syspreps + with metadata_monitor(session, image_os.meta): + image_os.do_sysprep() + infobox.finalize() + + # Disable syspreps that have ran + for sysprep in session['exec_syspreps']: + image_os.disable_sysprep(sysprep) + finally: + dev.umount() finally: - dev.umount() + dev.out.remove(infobox) finally: - out.cleanup() + infobox.cleanup() break return True @@ -705,9 +726,13 @@ def shrink(session): if not d.yesno("%s\n\nDo you want to continue?" % msg, width=70, height=12, title="Image Shrinking"): with metadata_monitor(session, dev.meta): - dev.out = InfoBoxOutput(d, "Image Shrinking", height=4) - dev.shrink() - dev.out.finalize() + infobox = InfoBoxOutput(d, "Image Shrinking", height=4) + dev.out.add(infobox) + try: + dev.shrink() + infobox.finalize() + finally: + dev.out.remove(infobox) session['shrinked'] = True update_background_title(session) @@ -784,50 +809,16 @@ def main_menu(session): actions[choice](session) -def select_file(d, media): - root = os.sep - while 1: - if media is not None: - if not os.path.exists(media): - d.msgbox("The file `%s' you choose does not exist." % media, - width=MSGBOX_WIDTH) - else: - break - - (code, media) = d.fselect(root, 10, 50, - title="Please select input media") - if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): - if confirm_exit(d, "You canceled the media selection dialog box."): - sys.exit(0) - else: - media = None - continue - - return media - - -def image_creator(d): - - usage = "Usage: %prog [options] [<input_media>]" - parser = optparse.OptionParser(version=version, usage=usage) - - options, args = parser.parse_args(sys.argv[1:]) - - if len(args) > 1: - parser.error("Wrong numver of arguments") +def image_creator(d, media, out): d.setBackgroundTitle('snf-image-creator') - if os.geteuid() != 0: - raise FatalError("You must run %s as root" % parser.get_prog_name()) - - media = select_file(d, args[0] if len(args) == 1 else None) - - out = GaugeOutput(d, "Initialization", "Initializing...") + gauge = GaugeOutput(d, "Initialization", "Initializing...") + out.add(gauge) disk = Disk(media, out) def signal_handler(signum, frame): - out.cleanup() + gauge.cleanup() disk.cleanup() signal.signal(signal.SIGINT, signal_handler) @@ -849,12 +840,13 @@ def image_creator(d): metadata[str(key)] = str(value) out.success("done") - out.cleanup() + gauge.cleanup() + out.remove(gauge) - # Make sure the signal handler does not call out.cleanup again + # Make sure the signal handler does not call gauge.cleanup again def dummy(self): pass - out.cleanup = type(GaugeOutput.cleanup)(dummy, out, GaugeOutput) + gauge.cleanup = type(GaugeOutput.cleanup)(dummy, gauge, GaugeOutput) session = {"dialog": d, "disk": disk, @@ -892,6 +884,28 @@ def image_creator(d): return 0 +def select_file(d, media): + root = os.sep + while 1: + if media is not None: + if not os.path.exists(media): + d.msgbox("The file `%s' you choose does not exist." % media, + width=MSGBOX_WIDTH) + else: + break + + (code, media) = d.fselect(root, 10, 50, + title="Please select input media") + if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): + if confirm_exit(d, "You canceled the media selection dialog box."): + sys.exit(0) + else: + media = None + continue + + return media + + def main(): d = dialog.Dialog(dialog="dialog") @@ -903,16 +917,53 @@ def main(): dialog._common_args_syntax["extra_label"] = \ lambda string: ("--extra-label", string) - while 1: - try: + usage = "Usage: %prog [options] [<input_media>]" + parser = optparse.OptionParser(version=version, usage=usage) + parser.add_option("-l", "--logfile", type="string", dest="logfile", + default=None, help="log all messages to FILE", + metavar="FILE") + + options, args = parser.parse_args(sys.argv[1:]) + + if len(args) > 1: + parser.error("Wrong number of arguments") + + d.setBackgroundTitle('snf-image-creator') + + try: + if os.geteuid() != 0: + raise FatalError("You must run %s as root" % \ + parser.get_prog_name()) + + media = select_file(d, args[0] if len(args) == 1 else None) + + logfile = None + if options.logfile is not None: try: - ret = image_creator(d) - sys.exit(ret) - except FatalError as e: - msg = textwrap.fill(str(e), width=70) - d.infobox(msg, width=INFOBOX_WIDTH, title="Fatal Error") - sys.exit(1) - except Reset: - continue + logfile = open(options.logfile, 'w') + except IOError as e: + raise FatalError( + "Unable to open logfile `%s' for writing. Reason: %s" % \ + (options.logfile, e.strerror)) + try: + log = SimpleOutput(False, logfile) if logfile is not None \ + else Output() + while 1: + try: + out = CombinedOutput([log]) + out.output("Starting %s version %s..." % \ + (parser.get_prog_name(), version)) + ret = image_creator(d, media, out) + sys.exit(ret) + except Reset: + log.output("Resetting everything...") + continue + finally: + if logfile is not None: + logfile.close() + except FatalError as e: + msg = textwrap.fill(str(e), width=70) + d.infobox(msg, width=INFOBOX_WIDTH, title="Fatal Error") + sys.exit(1) # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/image_creator/dialog_wizard.py b/image_creator/dialog_wizard.py index f283320305859a9883e5c774f64c516bfc8a409e..9359f23f5b1537d0e13f90777a1fd1efa7a7dc5a 100644 --- a/image_creator/dialog_wizard.py +++ b/image_creator/dialog_wizard.py @@ -213,66 +213,66 @@ def extract_image(session): image_os = session['image_os'] wizard = session['wizard'] - out = OutputWthProgress(True) - #Initialize the output - disk.out = out - device.out = out - image_os.out = out - - out.clear() - - #Sysprep - device.mount(False) - image_os.do_sysprep() - metadata = image_os.meta - device.umount() + with_progress = OutputWthProgress(True) + out = disk.out + out.add(with_progress) + try: + out.clear() - #Shrink - size = device.shrink() + #Sysprep + device.mount(False) + image_os.do_sysprep() + metadata = image_os.meta + device.umount() - metadata.update(device.meta) - metadata['DESCRIPTION'] = wizard['ImageDescription'] + #Shrink + size = device.shrink() - #MD5 - md5 = MD5(out) - session['checksum'] = md5.compute(snapshot, size) + metadata.update(device.meta) + metadata['DESCRIPTION'] = wizard['ImageDescription'] - #Metadata - metastring = '\n'.join( - ['%s=%s' % (key, value) for (key, value) in metadata.items()]) - metastring += '\n' + #MD5 + md5 = MD5(out) + session['checksum'] = md5.compute(snapshot, size) - out.output() - try: - out.output("Uploading image to pithos:") - kamaki = Kamaki(wizard['account'], wizard['token'], out) - - name = "%s-%s.diskdump" % (wizard['ImageName'], - time.strftime("%Y%m%d%H%M")) - pithos_file = "" - with open(snapshot, 'rb') as f: - pithos_file = kamaki.upload(f, size, name, - "(1/4) Calculating block hashes", - "(2/4) Uploading missing blocks") - - out.output("(3/4) Uploading metadata file...", False) - kamaki.upload(StringIO.StringIO(metastring), size=len(metastring), - remote_path="%s.%s" % (name, 'meta')) - out.success('done') - out.output("(4/4) Uploading md5sum file...", False) - md5sumstr = '%s %s\n' % (session['checksum'], name) - kamaki.upload(StringIO.StringIO(md5sumstr), size=len(md5sumstr), - remote_path="%s.%s" % (name, 'md5sum')) - out.success('done') - out.output() + #Metadata + metastring = '\n'.join( + ['%s=%s' % (key, value) for (key, value) in metadata.items()]) + metastring += '\n' - out.output('Registring image to ~okeanos...', False) - kamaki.register(wizard['ImageName'], pithos_file, metadata) - out.success('done') out.output() - - except ClientError as e: - raise FatalError("Pithos client: %d %s" % (e.status, e.message)) + try: + out.output("Uploading image to pithos:") + kamaki = Kamaki(wizard['account'], wizard['token'], out) + + name = "%s-%s.diskdump" % (wizard['ImageName'], + time.strftime("%Y%m%d%H%M")) + pithos_file = "" + with open(snapshot, 'rb') as f: + pithos_file = kamaki.upload(f, size, name, + "(1/4) Calculating block hashes", + "(2/4) Uploading missing blocks") + + out.output("(3/4) Uploading metadata file...", False) + kamaki.upload(StringIO.StringIO(metastring), size=len(metastring), + remote_path="%s.%s" % (name, 'meta')) + out.success('done') + out.output("(4/4) Uploading md5sum file...", False) + md5sumstr = '%s %s\n' % (session['checksum'], name) + kamaki.upload(StringIO.StringIO(md5sumstr), size=len(md5sumstr), + remote_path="%s.%s" % (name, 'md5sum')) + out.success('done') + out.output() + + out.output('Registring image to ~okeanos...', False) + kamaki.register(wizard['ImageName'], pithos_file, metadata) + out.success('done') + out.output() + + except ClientError as e: + raise FatalError("Pithos client: %d %s" % (e.status, e.message)) + finally: + out.remove(with_progress) msg = "The image was successfully uploaded and registered to " \ "~okeanos. Would you like to keep a local copy of the image?" diff --git a/image_creator/output/__init__.py b/image_creator/output/__init__.py index 3992846394939be20dce463899d6196cfbd932ae..0559242832fa6defe557f12c528db0e06f0b93b5 100644 --- a/image_creator/output/__init__.py +++ b/image_creator/output/__init__.py @@ -85,4 +85,59 @@ class Output(object): yield return generator + +class CombinedOutput(Output): + + def __init__(self, outputs=[]): + self._outputs = outputs + + def add(self, output): + self._outputs.append(output) + + def remove(self, output): + self._outputs.remove(output) + + def error(self, msg, new_line=True): + for out in self._outputs: + out.error(msg, new_line) + + def warn(self, msg, new_line=True): + for out in self._outputs: + out.warn(msg, new_line) + + def success(self, msg, new_line=True): + for out in self._outputs: + out.success(msg, new_line) + + def output(self, msg='', new_line=True): + for out in self._outputs: + out.output(msg, new_line) + + def cleanup(self): + for out in self._outputs: + out.cleanup() + + def clear(self): + for out in self._outputs: + out.clear() + + class _Progress(object): + + def __init__(self, size, title, bar_type='default'): + self.progresses = [] + for out in self.output._outputs: + self.progresses.append(out.Progress(size, title, bar_type)) + + def goto(self, dest): + for progress in self.progresses: + progress.goto(dest) + + def next(self): + for progress in self.progresses: + progress.next() + + def success(self, result): + for progress in self.progresses: + progress.success(result) + # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/image_creator/output/cli.py b/image_creator/output/cli.py index 37022d59bcdac04c86155e5ad56daf559f296117..91191cf943318d5dcf35937380c40a4c095b64cd 100644 --- a/image_creator/output/cli.py +++ b/image_creator/output/cli.py @@ -38,30 +38,30 @@ from colors import red, green, yellow from progress.bar import Bar -def output(msg='', new_line=True, decorate=lambda x: x): +def output(msg, new_line, decorate, stream): nl = "\n" if new_line else ' ' - sys.stderr.write(decorate(msg) + nl) + stream.write(decorate(msg) + nl) -def error(msg, new_line=True, colored=True): +def error(msg, new_line, colored, stream): color = red if colored else lambda x: x - output("Error: %s" % msg, new_line, color) + output("Error: %s" % msg, new_line, color, stream) -def warn(msg, new_line=True, colored=True): +def warn(msg, new_line, colored, stream): color = yellow if colored else lambda x: x - output("Warning: %s" % msg, new_line, color) + output("Warning: %s" % msg, new_line, color, stream) -def success(msg, new_line=True, colored=True): +def success(msg, new_line, colored, stream): color = green if colored else lambda x: x - output(msg, new_line, color) + output(msg, new_line, color, stream) -def clear(): +def clear(stream): #clear the page - if sys.stderr.isatty(): - sys.stderr.write('\033[H\033[2J') + if stream.isatty(): + stream.write('\033[H\033[2J') class SilentOutput(Output): @@ -69,23 +69,24 @@ class SilentOutput(Output): class SimpleOutput(Output): - def __init__(self, colored=True): + def __init__(self, colored=True, stream=None): self.colored = colored + self.stream = sys.stderr if stream is None else stream def error(self, msg, new_line=True): - error(msg, new_line, self.colored) + error(msg, new_line, self.colored, self.stream) def warn(self, msg, new_line=True): - warn(msg, new_line, self.colored) + warn(msg, new_line, self.colored, self.stream) def success(self, msg, new_line=True): - success(msg, new_line, self.colored) + success(msg, new_line, self.colored, self.stream) def output(self, msg='', new_line=True): - output(msg, new_line) + output(msg, new_line, lambda x: x, self.stream) def clear(self): - clear() + clear(self.stream) class OutputWthProgress(SimpleOutput): @@ -117,5 +118,4 @@ class OutputWthProgress(SimpleOutput): self.output.output("\r%s...\033[K" % self.title, False) self.output.success(result) - # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :