Add logging service in snf-image-creator-dialog
authorNikos Skalkotos <skalkoto@grnet.gr>
Wed, 22 Aug 2012 10:37:49 +0000 (13:37 +0300)
committerNikos Skalkotos <skalkoto@grnet.gr>
Wed, 22 Aug 2012 10:37:49 +0000 (13:37 +0300)
Logging can be enabled using the -l input option

image_creator/dialog_main.py
image_creator/dialog_wizard.py
image_creator/output/__init__.py
image_creator/output/cli.py

index 3082a1c..2ee0737 100644 (file)
@@ -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 :
index f283320..9359f23 100644 (file)
@@ -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?"
index 3992846..0559242 100644 (file)
@@ -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 :
index 37022d5..91191cf 100644 (file)
@@ -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 :