Here's an exciting and perhaps somewhat controversial patch set. This one removes support for saving bugs via scp and adds in its place support for saving directly to bugzilla. High points:
- Uses the product.bugUrl, with some mangling and checking. - Requires the user to already have an account. - Prompts for a summary so we don't have to make up something dumb. Figures out all the other bug fields automatically. - Attaches the complete traceback message. We can add whatever other files we want later on. - Attempts to detect duplicate bugs. I do this by concatenating the file name, function name, and text from each stack frame together and then hashing that. The hash is stored in the status_whiteboard. When I go to make a new bug, I first look for any bugs with the hash present. - When duplicates are found, the traceback is added as a comment and the user is put on the CC list. - Tries to bring up the network first if none is active.
What still needs to be done:
- Error handling needs a little more testing. - Right now I search for bugs where the status_whiteboard IS the hash. I need to be searching for bugs where the status_whiteboard CONTAINS the hash. I have ideas for how to do this from Will.
So, what does everyone think? Personally I'm a big fan of this because it removes some back-and-forth on getting bug information and doesn't require the user to have another computer set up and ready to scp things to.
- Chris
--- exception.py | 86 +------------------------------------------------- gui.py | 4 +-- text.py | 16 +-------- ui/exnSave.glade | 93 +---------------------------------------------------- 4 files changed, 6 insertions(+), 193 deletions(-)
diff --git a/exception.py b/exception.py index 724b7b9..e9c5695 100644 --- a/exception.py +++ b/exception.py @@ -189,76 +189,6 @@ def dumpException(out, text, tb, anaconda): out.write("\nException occurred during %s file copy:\n" % (file,)) traceback.print_exc(None, out)
-def scpAuthenticate(master, childpid, password): - while 1: - # Read up to password prompt. Propagate OSError exceptions, which - # can occur for anything that causes scp to immediately die (bad - # hostname, host down, etc.) - buf = os.read(master, 4096) - if buf.lower().find("password: ") != -1: - os.write(master, password+"\n") - # read the space and newline that get echoed back - os.read(master, 2) - break - - while 1: - buf = "" - try: - buf = os.read(master, 4096) - except (OSError, EOFError): - break - - (pid, childstatus) = os.waitpid (childpid, 0) - return childstatus - -# Save the traceback to a remote system via SCP. Returns success or not. -def copyExceptionToRemote(intf, scpInfo): - import pty - - (host, path, user, password) = scpInfo - - if host.find(":") != -1: - (host, port) = host.split(":") - - # Try to convert the port to an integer just as a check to see - # if it's a valid port number. If not, they'll get a chance to - # correct the information when scp fails. - try: - int(port) - portArgs = ["-P", port] - except ValueError: - portArgs = [] - else: - portArgs = [] - - # Thanks to Will Woods wwoods@redhat.com for the scp control - # here and in scpAuthenticate. - - # Fork ssh into its own pty - (childpid, master) = pty.fork() - if childpid < 0: - log.critical("Could not fork process to run scp") - return False - elif childpid == 0: - # child process - run scp - args = ["scp", "-oNumberOfPasswordPrompts=1", - "-oStrictHostKeyChecking=no"] + portArgs + \ - ["/tmp/anacdump.txt", "%s@%s:%s" % (user, host, path)] - os.execvp("scp", args) - - # parent process - try: - childstatus = scpAuthenticate(master, childpid, password) - except OSError: - return False - - os.close(master) - - if os.WIFEXITED(childstatus) and os.WEXITSTATUS(childstatus) == 0: - return True - else: - return False - # Save the traceback to a removable storage device, such as a floppy disk # or a usb/firewire drive. If there's no filesystem on the disk/partition, # write a vfat one. @@ -368,21 +298,7 @@ def runSaveDialog(anaconda, longTracebackFile): "disk.")) continue else: - scpInfo = saveWin.getDest() - scpSucceeded = copyExceptionToRemote(anaconda.intf, scpInfo) - - if scpSucceeded: - anaconda.intf.messageWindow(_("Dump Written"), - _("Your system's state has been successfully written to " - "the remote host. The installer will now exit."), - type="custom", custom_icon="info", - custom_buttons=[_("_Exit installer")]) - sys.exit(0) - else: - anaconda.intf.messageWindow(_("Dump Not Written"), - _("There was a problem writing the system state to the " - "remote host.")) - continue + continue elif rc == EXN_CANCEL: break
diff --git a/gui.py b/gui.py index 73a4233..cfa0960 100755 --- a/gui.py +++ b/gui.py @@ -732,8 +732,6 @@ class SaveExceptionWindow: def __init__(self, anaconda, longTracebackFile=None, screen=None): exnxml = gtk.glade.XML(findGladeFile("exnSave.glade"), domain="anaconda")
- self.hostEntry = exnxml.get_widget("hostEntry") - self.destEntry = exnxml.get_widget("destEntry") self.usernameEntry = exnxml.get_widget("usernameEntry") self.passwordEntry = exnxml.get_widget("passwordEntry")
@@ -799,7 +797,7 @@ class SaveExceptionWindow: elif self.saveToLocal(): return self.localChooser.get_filename() else: - return map(lambda e: e.get_text(), [self.hostEntry, self.destEntry, self.usernameEntry, self.passwordEntry]) + return map(lambda e: e.get_text(), [self.usernameEntry, self.passwordEntry])
def pop(self): self.window.destroy() diff --git a/text.py b/text.py index 35f491a..14ef53b 100644 --- a/text.py +++ b/text.py @@ -155,13 +155,9 @@ class SaveExceptionWindow:
def _destCb(self, *args): if self.rg.getSelection() == "disk": - self.hostEntry.setFlags(FLAG_DISABLED, FLAGS_SET) - self.destEntry.setFlags(FLAG_DISABLED, FLAGS_SET) self.usernameEntry.setFlags(FLAG_DISABLED, FLAGS_SET) self.passwordEntry.setFlags(FLAG_DISABLED, FLAGS_SET) else: - self.hostEntry.setFlags(FLAG_DISABLED, FLAGS_RESET) - self.destEntry.setFlags(FLAG_DISABLED, FLAGS_RESET) self.usernameEntry.setFlags(FLAG_DISABLED, FLAGS_RESET) self.passwordEntry.setFlags(FLAG_DISABLED, FLAGS_RESET)
@@ -175,7 +171,7 @@ class SaveExceptionWindow: if self.saveToDisk(): return self.diskList.current() else: - return map(lambda e: e.value(), [self.hostEntry, self.destEntry, self.usernameEntry, self.passwordEntry]) + return map(lambda e: e.value(), [self.usernameEntry, self.passwordEntry])
def pop(self): self.screen.popWindow() @@ -192,18 +188,12 @@ class SaveExceptionWindow: self.remoteButton.setCallback(self._destCb, None)
buttons = ButtonBar(self.screen, [TEXT_OK_BUTTON, TEXT_CANCEL_BUTTON]) - self.hostEntry = Entry(24) - self.destEntry = Entry(24) self.usernameEntry = Entry(24) self.passwordEntry = Entry(24, password=1)
self.diskList = Listbox(height=3, scroll=1)
- remoteGrid = Grid(2, 4) - remoteGrid.setField(Label(_("Host")), 0, 0, anchorLeft=1) - remoteGrid.setField(self.hostEntry, 1, 0) - remoteGrid.setField(Label(_("Remote path")), 0, 1, anchorLeft=1) - remoteGrid.setField(self.destEntry, 1, 1) + remoteGrid = Grid(2, 2) remoteGrid.setField(Label(_("User name")), 0, 2, anchorLeft=1) remoteGrid.setField(self.usernameEntry, 1, 2) remoteGrid.setField(Label(_("Password")), 0, 3, anchorLeft=1) @@ -223,8 +213,6 @@ class SaveExceptionWindow:
# self.diskList.setCurrent("sda")
- self.hostEntry.setFlags(FLAG_DISABLED, FLAGS_SET) - self.destEntry.setFlags(FLAG_DISABLED, FLAGS_SET) self.usernameEntry.setFlags(FLAG_DISABLED, FLAGS_SET) self.passwordEntry.setFlags(FLAG_DISABLED, FLAGS_SET) else: diff --git a/ui/exnSave.glade b/ui/exnSave.glade index b7ba57a..0bab909 100644 --- a/ui/exnSave.glade +++ b/ui/exnSave.glade @@ -74,7 +74,7 @@ <child> <widget class="GtkLabel" id="label6"> <property name="visible">True</property> - <property name="label" translatable="yes">Select a destination for the exception information.</property> + <property name="label" translatable="yes">Your traceback can either be saved to a local disk or to a bug tracking system.</property> <property name="use_underline">False</property> <property name="use_markup">False</property> <property name="justify">GTK_JUSTIFY_LEFT</property> @@ -228,7 +228,7 @@ <widget class="GtkRadioButton" id="remoteButton"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="label" translatable="yes">_Remote (scp)</property> + <property name="label" translatable="yes">_Remote (%s)</property> <property name="use_underline">True</property> <property name="relief">GTK_RELIEF_NORMAL</property> <property name="focus_on_click">True</property> @@ -271,56 +271,6 @@ <property name="spacing">5</property>
<child> - <widget class="GtkLabel" id="label2"> - <property name="visible">True</property> - <property name="label">Host (host:port)</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">True</property> - </packing> - </child> - - <child> - <widget class="GtkLabel" id="label3"> - <property name="visible">True</property> - <property name="label">Destination file</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> - <property name="width_chars">-1</property> - <property name="single_line_mode">False</property> - <property name="angle">0</property> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">True</property> - </packing> - </child> - - <child> <widget class="GtkLabel" id="label4"> <property name="visible">True</property> <property name="label">User name</property> @@ -385,45 +335,6 @@ <property name="spacing">5</property>
<child> - <widget class="GtkEntry" id="hostEntry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="has_focus">True</property> - <property name="editable">True</property> - <property name="visibility">True</property> - <property name="max_length">0</property> - <property name="text" translatable="yes"></property> - <property name="has_frame">True</property> - <property name="invisible_char">•</property> - <property name="activates_default">False</property> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - - <child> - <widget class="GtkEntry" id="destEntry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="editable">True</property> - <property name="visibility">True</property> - <property name="max_length">0</property> - <property name="text" translatable="yes"></property> - <property name="has_frame">True</property> - <property name="invisible_char">•</property> - <property name="activates_default">False</property> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - - <child> <widget class="GtkEntry" id="usernameEntry"> <property name="visible">True</property> <property name="can_focus">True</property>
The purpose of this class is to package up the Python representation of a traceback along with some methods for doing all the dumping and mangling that we need to do. In particular, now there's fewer values to pass around to the various functions in the exn saving dialogs. --- exception.py | 319 ++++++++++++++++++++++++++++++--------------------------- 1 files changed, 168 insertions(+), 151 deletions(-)
diff --git a/exception.py b/exception.py index e9c5695..fa87389 100644 --- a/exception.py +++ b/exception.py @@ -43,151 +43,179 @@ import kickstart import logging log = logging.getLogger("anaconda")
-dumpHash = {} - -# XXX do length limits on obj dumps. -def dumpClass(instance, fd, level=0, parentkey="", skipList=[]): - # protect from loops - try: - if not dumpHash.has_key(instance): - dumpHash[instance] = None - else: - fd.write("Already dumped\n") - return - except TypeError: - fd.write("Cannot dump object\n") - return - - if (instance.__class__.__dict__.has_key("__str__") or - instance.__class__.__dict__.has_key("__repr__")): - fd.write("%s\n" % (instance,)) - return - fd.write("%s instance, containing members:\n" % - (instance.__class__.__name__)) - pad = ' ' * ((level) * 2) - - for key, value in instance.__dict__.items(): - if parentkey != "": - curkey = parentkey + "." + key - else: - curkey = key - - # Don't dump objects that are in our skip list, though ones that are - # None are probably okay. - if eval("instance.%s is not None" % key) and \ - eval("id(instance.%s)" % key) in skipList: - continue - - if type(value) == types.ListType: - fd.write("%s%s: [" % (pad, curkey)) - first = 1 - for item in value: - if not first: - fd.write(", ") - else: - first = 0 - if type(item) == types.InstanceType: - dumpClass(item, fd, level + 1, skipList=skipList) - else: - fd.write("%s" % (item,)) - fd.write("]\n") - elif type(value) == types.DictType: - fd.write("%s%s: {" % (pad, curkey)) - first = 1 - for k, v in value.items(): - if not first: - fd.write(", ") - else: - first = 0 - if type(k) == types.StringType: - fd.write("'%s': " % (k,)) - else: - fd.write("%s: " % (k,)) - if type(v) == types.InstanceType: - dumpClass(v, fd, level + 1, parentkey = curkey, skipList=skipList) - else: - fd.write("%s" % (v,)) - fd.write("}\n") - elif type(value) == types.InstanceType: - fd.write("%s%s: " % (pad, curkey)) - dumpClass(value, fd, level + 1, parentkey=curkey, skipList=skipList) - else: - fd.write("%s%s: %s\n" % (pad, curkey, value)) - -def dumpException(out, text, tb, anaconda): - skipList = [ "anaconda.backend.ayum", - "anaconda.backend.dlpkgs", - "anaconda.id.accounts", - "anaconda.id.bootloader.password", - "anaconda.id.comps", - "anaconda.id.dispatch", - "anaconda.id.hdList", - "anaconda.id.ksdata.bootloader", - "anaconda.id.ksdata.rootpw", - "anaconda.id.ksdata.vnc", - "anaconda.id.instLanguage.font", - "anaconda.id.instLanguage.kbd", - "anaconda.id.instLanguage.info", - "anaconda.id.instLanguage.localeInfo", - "anaconda.id.instLanguage.nativeLangNames", - "anaconda.id.instLanguage.tz", - "anaconda.id.keyboard._mods._modelDict", - "anaconda.id.keyboard.modelDict", - "anaconda.id.rootPassword", - "anaconda.id.tmpData", - "anaconda.intf.icw.buff", - "anaconda.intf.icw.stockButtons", - "dispatch.sack.excludes", - ] - idSkipList = [] - - # Catch attributes that do not exist at the time we do the exception dump - # and ignore them. - for k in skipList: +class AnacondaExceptionDump: + def __init__(self, type, value, tb): + self.type = type + self.value = value + self.tb = tb + + self._dumpHash = {} + + # Reverse the order that tracebacks are printed so people will hopefully quit + # giving us the least useful part of the exception in bug reports. + def __str__(self): + lst = traceback.format_tb(self.tb) + lst.reverse() + lst.insert(0, "anaconda %s exception report\n" % os.getenv("ANACONDAVERSION")) + lst.insert(1, 'Traceback (most recent call first):\n') + lst.extend(traceback.format_exception_only(self.type, self.value)) + return joinfields(lst, "") + + # Create a string representation of a class and write it to fd. This + # method will recursively handle all attributes of the base given class. + def _dumpClass(self, instance, fd, level=0, parentkey="", skipList=[]): + # protect from loops try: - eval("idSkipList.append(id(%s))" % k) - except: - pass + if not self._dumpHash.has_key(instance): + self._dumpHash[instance] = None + else: + fd.write("Already dumped\n") + return + except TypeError: + fd.write("Cannot dump object\n") + return
- p = Pickler(out) + if (instance.__class__.__dict__.has_key("__str__") or + instance.__class__.__dict__.has_key("__repr__")): + fd.write("%s\n" % (instance,)) + return + fd.write("%s instance, containing members:\n" % + (instance.__class__.__name__)) + pad = ' ' * ((level) * 2)
- out.write(text) + for key, value in instance.__dict__.items(): + if parentkey != "": + curkey = parentkey + "." + key + else: + curkey = key
- trace = tb - if trace is not None: - while trace.tb_next: - trace = trace.tb_next - frame = trace.tb_frame - out.write ("\nLocal variables in innermost frame:\n") - try: - for (key, value) in frame.f_locals.items(): - out.write ("%s: %s\n" % (key, value)) - except: - pass + # Don't dump objects that are in our skip list, though ones that are + # None are probably okay. + if eval("instance.%s is not None" % key) and \ + eval("id(instance.%s)" % key) in skipList: + continue
- try: - out.write("\n\n") - dumpClass(anaconda, out, skipList=idSkipList) - except: - out.write("\nException occurred during state dump:\n") - traceback.print_exc(None, out) + if type(value) == types.ListType: + fd.write("%s%s: [" % (pad, curkey)) + first = 1 + for item in value: + if not first: + fd.write(", ") + else: + first = 0 + if type(item) == types.InstanceType: + self._dumpClass(item, fd, level + 1, skipList=skipList) + else: + fd.write("%s" % (item,)) + fd.write("]\n") + elif type(value) == types.DictType: + fd.write("%s%s: {" % (pad, curkey)) + first = 1 + for k, v in value.items(): + if not first: + fd.write(", ") + else: + first = 0 + if type(k) == types.StringType: + fd.write("'%s': " % (k,)) + else: + fd.write("%s: " % (k,)) + if type(v) == types.InstanceType: + self._dumpClass(v, fd, level + 1, parentkey = curkey, skipList=skipList) + else: + fd.write("%s" % (v,)) + fd.write("}\n") + elif type(value) == types.InstanceType: + fd.write("%s%s: " % (pad, curkey)) + self._dumpClass(value, fd, level + 1, parentkey=curkey, skipList=skipList) + else: + fd.write("%s%s: %s\n" % (pad, curkey, value)) + + # Dump the python traceback, internal state, and several files to the given + # file descriptor. + def dump (self, fd, anaconda): + skipList = [ "anaconda.backend.ayum", + "anaconda.backend.dlpkgs", + "anaconda.id.accounts", + "anaconda.id.bootloader.password", + "anaconda.id.comps", + "anaconda.id.dispatch", + "anaconda.id.hdList", + "anaconda.id.ksdata.bootloader", + "anaconda.id.ksdata.rootpw", + "anaconda.id.ksdata.vnc", + "anaconda.id.instLanguage.font", + "anaconda.id.instLanguage.kbd", + "anaconda.id.instLanguage.info", + "anaconda.id.instLanguage.localeInfo", + "anaconda.id.instLanguage.nativeLangNames", + "anaconda.id.instLanguage.tz", + "anaconda.id.keyboard._mods._modelDict", + "anaconda.id.keyboard.modelDict", + "anaconda.id.rootPassword", + "anaconda.id.tmpData", + "anaconda.intf.icw.buff", + "anaconda.intf.icw.stockButtons", + "dispatch.sack.excludes", + ] + idSkipList = [] + + # Catch attributes that do not exist at the time we do the exception dump + # and ignore them. + for k in skipList: + try: + eval("idSkipList.append(id(%s))" % k) + except: + pass + + p = Pickler(fd) + + fd.write(str(self)) + + trace = self.tb + if trace is not None: + while trace.tb_next: + trace = trace.tb_next + frame = trace.tb_frame + fd.write ("\nLocal variables in innermost frame:\n") + try: + for (key, value) in frame.f_locals.items(): + fd.write ("%s: %s\n" % (key, value)) + except: + pass
- for file in ("/tmp/syslog", "/tmp/anaconda.log", "/tmp/netinfo", - "/tmp/lvmout", "/tmp/resize.out", - anaconda.rootPath + "/root/install.log", - anaconda.rootPath + "/root/upgrade.log"): try: - f = open(file, 'r') - line = "\n\n%s:\n" % (file,) - while line: - out.write(line) - line = f.readline() - f.close() - except IOError: - pass + fd.write("\n\n") + self._dumpClass(anaconda, fd, skipList=idSkipList) except: - out.write("\nException occurred during %s file copy:\n" % (file,)) - traceback.print_exc(None, out) + fd.write("\nException occurred during state dump:\n") + traceback.print_exc(None, fd) + + for file in ("/tmp/syslog", "/tmp/anaconda.log", "/tmp/netinfo", + "/tmp/lvmout", "/tmp/resize.out", + anaconda.rootPath + "/root/install.log", + anaconda.rootPath + "/root/upgrade.log"): + try: + f = open(file, 'r') + line = "\n\n%s:\n" % (file,) + while line: + fd.write(line) + line = f.readline() + f.close() + except IOError: + pass + except: + fd.write("\nException occurred during %s file copy:\n" % (file,)) + traceback.print_exc(None, fd) + + def hash(self): + import hashlib + s = "" + + for (file, lineno, func, text) in traceback.extract_tb(self.tb): + s += "%s %s %s\n" % (file, func, text) + + return hashlib.sha256(s).hexdigest()
# Save the traceback to a removable storage device, such as a floppy disk # or a usb/firewire drive. If there's no filesystem on the disk/partition, @@ -243,16 +271,6 @@ def copyExceptionToDisk(anaconda, device): isys.umount("/tmp/crash") return True
-# Reverse the order that tracebacks are printed so people will hopefully quit -# giving us the least useful part of the exception in bug reports. -def formatException (type, value, tb): - lst = traceback.format_tb(tb) - lst.reverse() - lst.insert(0, "anaconda %s exception report\n" % os.getenv("ANACONDAVERSION")) - lst.insert(1, 'Traceback (most recent call first):\n') - lst.extend(traceback.format_exception_only(type, value)) - return lst - def runSaveDialog(anaconda, longTracebackFile): saveWin = anaconda.intf.saveExceptionWindow(anaconda, longTracebackFile) if not saveWin: @@ -311,13 +329,12 @@ def handleException(anaconda, (type, value, tb)): # restore original exception handler sys.excepthook = sys.__excepthook__
- # get traceback information - list = formatException (type, value, tb) - text = joinfields (list, "") + exn = AnacondaExceptionDump(type, value, tb) + text = str(exn)
# save to local storage first out = open("/tmp/anacdump.txt", "w") - dumpException (out, text, tb, anaconda) + exn.dump(out, anaconda) out.close()
# see if /mnt/sysimage is present and put exception there as well
This patch adds support for save to bugzilla, using the python-bugzilla module. We get the bugzilla URL from product.bugUrl and require the user to already have a valid account with that bugzilla instance. That should cut down on potential abuse.
To cut down on the number of possible duplicates, we hash the file name, function name, and line of each frame in the traceback and store that hash in the bug itself. Before filing a new bug, we query for any bugs containing that hash value. If found, we simply add the traceback as a new attachment and put the user on the CC list. If not found, we create a new bug. Either way, the user is encouraged to visit their bug and make a more meaningful comment. --- exception.py | 121 ++++++++++++++++++++++++++++++++++++++++++++++++----- gui.py | 6 ++- text.py | 21 ++++++--- ui/exnSave.glade | 52 +++++++++++++++++++++-- 4 files changed, 176 insertions(+), 24 deletions(-)
diff --git a/exception.py b/exception.py index fa87389..b6913a4 100644 --- a/exception.py +++ b/exception.py @@ -49,6 +49,8 @@ class AnacondaExceptionDump: self.value = value self.tb = tb
+ self.tbFile = None + self._dumpHash = {}
# Reverse the order that tracebacks are printed so people will hopefully quit @@ -217,6 +219,12 @@ class AnacondaExceptionDump:
return hashlib.sha256(s).hexdigest()
+ def write(self, anaconda): + self.tbFile = "/tmp/anacdump.txt" + fd = open(self.tbFile, "w") + self.dump(fd, anaconda) + fd.close() + # Save the traceback to a removable storage device, such as a floppy disk # or a usb/firewire drive. If there's no filesystem on the disk/partition, # write a vfat one. @@ -271,8 +279,99 @@ def copyExceptionToDisk(anaconda, device): isys.umount("/tmp/crash") return True
-def runSaveDialog(anaconda, longTracebackFile): - saveWin = anaconda.intf.saveExceptionWindow(anaconda, longTracebackFile) +def saveToBugzilla(anaconda, exn, dest): + import bugzilla, xmlrpclib + import product, rpmUtils + + if dest[0].strip() == "" or dest[1].strip() == "" or dest[2].strip() == "": + anaconda.intf.messageWindow(_("Invalid Bug Information"), + _("Please provide a valid username, " + "password, and short bug description.")) + return False + + hash = exn.hash() + + if product.bugUrl.startswith("http://"): + bugUrl = "https://" + product.bugUrl[7:] + elif product.bugUrl.startswith("https://"): + bugUrl = product.bugUrl + else: + anaconda.intf.messageWindow(_("No bugzilla URL"), + _("Your distribution does not provide a " + "bug reporting URL, so you cannot save " + "your exception to a remote bug tracking " + "system.")) + return False + + if not exn.tbFile: + exn.write(anaconda) + + bz = bugzilla.Bugzilla(url = "%s/xmlrpc.cgi" % bugUrl) + + if not bz.login(dest[0], dest[1]): + anaconda.intf.messageWindow(_("Unable To Login"), + _("There was an error logging into %s " + "using the provided username and " + "password.") % product.bugUrl) + return False + + # Are there any existing bugs with this hash value? If so we will just + # add this traceback to the bug report and put the reporter on the CC + # list. Otherwise, we need to create a new bug. + try: + buglist = bz.query({'status_whiteboard': hash}) + except xmlrpclib.ProtocolError, e: + anaconda.intf.messageWindow(_("Unable To File Bug"), + _("Your bug could not be filed due to the " + "following error when communicating with " + "bugzilla:\n\n%s" % str(e))) + return False + + # FIXME: need to handle all kinds of errors here + if len(buglist) == 0: + bug = bz.createbug(product=product.productName, + component="anaconda", + version=product.productVersion, + rep_platform=rpmUtils.arch.getBaseArch(), + bug_severity="medium", + priority="medium", + op_sys="Linux", + bug_file_loc="http://", + short_desc=dest[2], + comment="This bug was filed automatically by anaconda.") + bug.setwhiteboard("anaconda_trace_hash:%s" % hash, which="status") + bz.attachfile(bug.bug_id, exn.tbFile, "Attached traceback automatically from anaconda.", + contenttype="text/plain") + + # Tell the user we created a new bug for them and that they should + # go add a descriptive comment. + anaconda.intf.messageWindow(_("Bug Created"), + _("A new bug has been created with your traceback attached. " + "Please add additional information such as what you were doing " + "when you encountered the bug, screenshots, and whatever else " + "is appropriate to the following bug:\n\n%s/%s") % (bugUrl, bug.bug_id), + type="custom", custom_icon="info", + custom_buttons=[_("_Exit installer")]) + sys.exit(0) + else: + id = buglist[0].bug_id + bz.attachfile(id, exn.tbFile, "Attached traceback automatically from anaconda.", + contenttype="text/plain") + bz._updatecc(id, [dest[0]], "add") + + # Tell the user which bug they've been CC'd on and that they should + # go add a descriptive comment. + anaconda.intf.messageWindow(_("Bug Updated"), + _("A bug with your information already exists. Your account has " + "been added to the CC list and your traceback added as a " + "comment. Please add additional descriptive information to the " + "following bug:\n\n%s/%s") % (bugUrl, id), + type="custom", custom_icon="info", + custom_buttons=[_("_Exit installer")]) + sys.exit(0) + +def runSaveDialog(anaconda, exn): + saveWin = anaconda.intf.saveExceptionWindow(anaconda, exn.tbFile) if not saveWin: anaconda.intf.__del__() os.kill(os.getpid(), signal.SIGKILL) @@ -301,7 +400,7 @@ def runSaveDialog(anaconda, longTracebackFile): elif saveWin.saveToLocal(): dest = saveWin.getDest() try: - shutil.copyfile("/tmp/anacdump.txt", "%s/InstallError.txt" %(dest,)) + shutil.copyfile(exn.tbFile, "%s/InstallError.txt" %(dest,)) anaconda.intf.messageWindow(_("Dump Written"), _("Your system's state has been successfully written to " "the disk. The installer will now exit."), @@ -309,14 +408,15 @@ def runSaveDialog(anaconda, longTracebackFile): custom_buttons=[_("_Exit installer")]) sys.exit(0) except Exception, e: - log.error("Failed to copy anacdump.txt to %s/anacdump.txt: %s" %(dest, e)) + log.error("Failed to copy %s to %s/anacdump.txt: %s" %(exn.tbFile, dest, e)) else: anaconda.intf.messageWindow(_("Dump Not Written"), _("There was a problem writing the system state to the " "disk.")) continue else: - continue + if not saveToBugzilla(anaconda, exn, saveWin.getDest()): + continue elif rc == EXN_CANCEL: break
@@ -329,14 +429,11 @@ def handleException(anaconda, (type, value, tb)): # restore original exception handler sys.excepthook = sys.__excepthook__
+ # Save the exception file to local storage first. exn = AnacondaExceptionDump(type, value, tb) + exn.write(anaconda) text = str(exn)
- # save to local storage first - out = open("/tmp/anacdump.txt", "w") - exn.dump(out, anaconda) - out.close() - # see if /mnt/sysimage is present and put exception there as well if os.access("/mnt/sysimage/root", os.X_OK): try: @@ -352,7 +449,7 @@ def handleException(anaconda, (type, value, tb)): except: pass
- mainWin = anaconda.intf.mainExceptionWindow(text, "/tmp/anacdump.txt") + mainWin = anaconda.intf.mainExceptionWindow(text, exn.tbFile) if not mainWin: anaconda.intf.__del__() os.kill(os.getpid(), signal.SIGKILL) @@ -396,4 +493,4 @@ def handleException(anaconda, (type, value, tb)): pdb.post_mortem (tb) os.kill(os.getpid(), signal.SIGKILL) elif rc == EXN_SAVE: - runSaveDialog(anaconda, "/tmp/anacdump.txt") + runSaveDialog(anaconda, exn) diff --git a/gui.py b/gui.py index cfa0960..aebe05e 100755 --- a/gui.py +++ b/gui.py @@ -734,6 +734,7 @@ class SaveExceptionWindow:
self.usernameEntry = exnxml.get_widget("usernameEntry") self.passwordEntry = exnxml.get_widget("passwordEntry") + self.bugDesc = exnxml.get_widget("bugDesc")
self.diskButton = exnxml.get_widget("diskButton") self.diskCombo = exnxml.get_widget("diskCombo") @@ -747,6 +748,8 @@ class SaveExceptionWindow: self.remoteButton.connect("toggled", self.radio_changed) self.localButton.connect("toggled", self.radio_changed)
+ self.remoteButton.set_label(self.remoteButton.get_label() % product.bugUrl) + cell = gtk.CellRendererText() self.diskCombo.pack_start(cell, True) self.diskCombo.set_attributes(cell, text=1) @@ -797,7 +800,8 @@ class SaveExceptionWindow: elif self.saveToLocal(): return self.localChooser.get_filename() else: - return map(lambda e: e.get_text(), [self.usernameEntry, self.passwordEntry]) + return map(lambda e: e.get_text(), [self.usernameEntry, self.passwordEntry, + self.bugDesc])
def pop(self): self.window.destroy() diff --git a/text.py b/text.py index 14ef53b..304235b 100644 --- a/text.py +++ b/text.py @@ -29,6 +29,7 @@ import iutil import time import signal import parted +import product import string from language import expandLangs from flags import flags @@ -157,9 +158,11 @@ class SaveExceptionWindow: if self.rg.getSelection() == "disk": self.usernameEntry.setFlags(FLAG_DISABLED, FLAGS_SET) self.passwordEntry.setFlags(FLAG_DISABLED, FLAGS_SET) + self.bugDesc.setFlags(FLAG_DISABLED, FLAGS_SET) else: self.usernameEntry.setFlags(FLAG_DISABLED, FLAGS_RESET) self.passwordEntry.setFlags(FLAG_DISABLED, FLAGS_RESET) + self.bugDesc.setFlags(FLAG_DISABLED, FLAGS_RESET)
def getrc(self): if self.rc == TEXT_OK_CHECK: @@ -171,7 +174,8 @@ class SaveExceptionWindow: if self.saveToDisk(): return self.diskList.current() else: - return map(lambda e: e.value(), [self.usernameEntry, self.passwordEntry]) + return map(lambda e: e.value(), [self.usernameEntry, self.passwordEntry, + self.bugDesc])
def pop(self): self.screen.popWindow() @@ -182,7 +186,7 @@ class SaveExceptionWindow:
self.rg = RadioGroup() self.diskButton = self.rg.add(_("Save to Disk"), "disk", True) - self.remoteButton = self.rg.add(_("Save to Remote"), "remote", False) + self.remoteButton = self.rg.add(_("Save to Remote (%s)") % product.bugUrl, "remote", False)
self.diskButton.setCallback(self._destCb, None) self.remoteButton.setCallback(self._destCb, None) @@ -190,14 +194,17 @@ class SaveExceptionWindow: buttons = ButtonBar(self.screen, [TEXT_OK_BUTTON, TEXT_CANCEL_BUTTON]) self.usernameEntry = Entry(24) self.passwordEntry = Entry(24, password=1) + self.bugDesc = Entry(24)
self.diskList = Listbox(height=3, scroll=1)
- remoteGrid = Grid(2, 2) - remoteGrid.setField(Label(_("User name")), 0, 2, anchorLeft=1) - remoteGrid.setField(self.usernameEntry, 1, 2) - remoteGrid.setField(Label(_("Password")), 0, 3, anchorLeft=1) - remoteGrid.setField(self.passwordEntry, 1, 3) + remoteGrid = Grid(2, 3) + remoteGrid.setField(Label(_("User name")), 0, 0, anchorLeft=1) + remoteGrid.setField(self.usernameEntry, 1, 0) + remoteGrid.setField(Label(_("Password")), 0, 1, anchorLeft=1) + remoteGrid.setField(self.passwordEntry, 1, 1) + remoteGrid.setField(Label(_("Bug Description")), 0, 2, anchorLeft=1) + remoteGrid.setField(self.bugDesc, 1, 2)
toplevel.add(self.diskButton, 0, 0, (0, 0, 0, 1)) toplevel.add(self.diskList, 0, 1, (0, 0, 0, 1)) diff --git a/ui/exnSave.glade b/ui/exnSave.glade index 0bab909..6c22e2f 100644 --- a/ui/exnSave.glade +++ b/ui/exnSave.glade @@ -273,8 +273,8 @@ <child> <widget class="GtkLabel" id="label4"> <property name="visible">True</property> - <property name="label">User name</property> - <property name="use_underline">False</property> + <property name="label">_User name</property> + <property name="use_underline">True</property> <property name="use_markup">False</property> <property name="justify">GTK_JUSTIFY_LEFT</property> <property name="wrap">False</property> @@ -298,8 +298,8 @@ <child> <widget class="GtkLabel" id="label5"> <property name="visible">True</property> - <property name="label">Password</property> - <property name="use_underline">False</property> + <property name="label">_Password</property> + <property name="use_underline">True</property> <property name="use_markup">False</property> <property name="justify">GTK_JUSTIFY_LEFT</property> <property name="wrap">False</property> @@ -319,6 +319,31 @@ <property name="fill">True</property> </packing> </child> + + <child> + <widget class="GtkLabel" id="label7"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Bug description</property> + <property name="use_underline">True</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_LEFT</property> + <property name="wrap">False</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> + <property name="width_chars">-1</property> + <property name="single_line_mode">False</property> + <property name="angle">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> </widget> <packing> <property name="padding">0</property> @@ -371,6 +396,25 @@ <property name="fill">True</property> </packing> </child> + + <child> + <widget class="GtkEntry" id="bugDesc"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="editable">True</property> + <property name="visibility">True</property> + <property name="max_length">0</property> + <property name="text" translatable="yes"></property> + <property name="has_frame">True</property> + <property name="invisible_char">•</property> + <property name="activates_default">False</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> </widget> <packing> <property name="padding">0</property>
--- exception.py | 63 ++++++++++++++++++++++++++++++++++++--------------------- 1 files changed, 40 insertions(+), 23 deletions(-)
diff --git a/exception.py b/exception.py index b6913a4..75aff94 100644 --- a/exception.py +++ b/exception.py @@ -283,6 +283,24 @@ def saveToBugzilla(anaconda, exn, dest): import bugzilla, xmlrpclib import product, rpmUtils
+ def withBugzillaDo(bz, fn): + try: + retval = fn(bz) + return retval + except xmlrpclib.ProtocolError, e: + msg = _("Your bug could not be filed due to the following error " + "when communicating with bugzilla:\n\n%s" % str(e)) + except xmlrpclib.Fault, e: + msg = _("Your bug could not be filed due to bad information in " + "the bug fields. This is most likely an error in " + "anaconda:\n\n%s" % str(e)) + except socket.error, e: + msg = _("Your bug could not be filed due to networking problems " + "when communicating with bugzilla:\n\n%s" % str(e)) + + anaconda.intf.messageWindow(_("Unable To File Bug"), msg) + return None + if dest[0].strip() == "" or dest[1].strip() == "" or dest[2].strip() == "": anaconda.intf.messageWindow(_("Invalid Bug Information"), _("Please provide a valid username, " @@ -318,30 +336,28 @@ def saveToBugzilla(anaconda, exn, dest): # Are there any existing bugs with this hash value? If so we will just # add this traceback to the bug report and put the reporter on the CC # list. Otherwise, we need to create a new bug. - try: - buglist = bz.query({'status_whiteboard': hash}) - except xmlrpclib.ProtocolError, e: - anaconda.intf.messageWindow(_("Unable To File Bug"), - _("Your bug could not be filed due to the " - "following error when communicating with " - "bugzilla:\n\n%s" % str(e))) + buglist = withBugzillaDo(bz, lambda b: b.query({'status_whiteboard': hash})) + if buglist is None: return False
- # FIXME: need to handle all kinds of errors here if len(buglist) == 0: - bug = bz.createbug(product=product.productName, - component="anaconda", - version=product.productVersion, - rep_platform=rpmUtils.arch.getBaseArch(), - bug_severity="medium", - priority="medium", - op_sys="Linux", - bug_file_loc="http://", - short_desc=dest[2], - comment="This bug was filed automatically by anaconda.") + bug = withBugzillaDo(bz, lambda b: b.createbug(product=product.productName, + component="anaconda", + version=product.productVersion, + rep_platform=rpmUtils.arch.getBaseArch(), + bug_severity="medium", + priority="medium", + op_sys="Linux", + bug_file_loc="http://", + short_desc=dest[2], + comment="This bug was filed automatically by anaconda.")) + if bug is None: + return False + bug.setwhiteboard("anaconda_trace_hash:%s" % hash, which="status") - bz.attachfile(bug.bug_id, exn.tbFile, "Attached traceback automatically from anaconda.", - contenttype="text/plain") + withBugzillaDo(bz, lambda b: b.attachfile(bug.bug_id, exn.tbFile, + "Attached traceback automatically from anaconda.", + contenttype="text/plain"))
# Tell the user we created a new bug for them and that they should # go add a descriptive comment. @@ -355,9 +371,10 @@ def saveToBugzilla(anaconda, exn, dest): sys.exit(0) else: id = buglist[0].bug_id - bz.attachfile(id, exn.tbFile, "Attached traceback automatically from anaconda.", - contenttype="text/plain") - bz._updatecc(id, [dest[0]], "add") + withBugzillaDo(bz, lambda b: b.attachfile(id, exn.tbFile, + "Attached traceback automatically from anaconda.", + contenttype="text/plain")) + withBugzillaDo(bz, lambda b: b._updatecc(id, [dest[0]], "add"))
# Tell the user which bug they've been CC'd on and that they should # go add a descriptive comment.
--- exception.py | 18 ++++++++++++++++++ 1 files changed, 18 insertions(+), 0 deletions(-)
diff --git a/exception.py b/exception.py index 75aff94..8417ee7 100644 --- a/exception.py +++ b/exception.py @@ -432,6 +432,24 @@ def runSaveDialog(anaconda, exn): "disk.")) continue else: + import network + if not network.hasActiveNetDev(): + if os.environ.has_key("DISPLAY"): + from netconfig_dialog import NetworkConfigurator + import gtk + + net = NetworkConfigurator(anaconda.id.network) + ret = net.run() + net.destroy() + + if ret != gtk.RESPONSE_CANCEL: + continue + else: + anaconda.intf.messageWindow(_("No Network Available"), + _("Cannot save a bug report since there is no " + "active networking device available.")) + continue + if not saveToBugzilla(anaconda, exn, saveWin.getDest()): continue elif rc == EXN_CANCEL:
--- anaconda.spec | 1 + scripts/upd-instroot | 3 ++- 2 files changed, 3 insertions(+), 1 deletions(-)
diff --git a/anaconda.spec b/anaconda.spec index 19dc803..c1dc83b 100644 --- a/anaconda.spec +++ b/anaconda.spec @@ -107,6 +107,7 @@ Requires: system-config-keyboard %endif Requires: hal, dbus-python Requires: cracklib-python +Requires: python-bugzilla %ifarch %livearches Requires: usermode Requires: zenity diff --git a/scripts/upd-instroot b/scripts/upd-instroot index 1645aad..334375d 100755 --- a/scripts/upd-instroot +++ b/scripts/upd-instroot @@ -165,7 +165,7 @@ PACKAGES="glibc-common setup python newt slang libselinux device-mapper device-mapper-libs dmraid keyutils-libs libsemanage-python python-pyblock mkinitrd libbdevid libbdevid-python nss nspr pcre cryptsetup-luks libgcrypt libgpg-error dbus dbus-python hal - cracklib-python module-init-tools cracklib-dicts" + cracklib-python module-init-tools cracklib-dicts python-bugzilla"
if [ $ARCH = i386 ]; then PACKAGES="$PACKAGES glibc.i386 openssl.i386" @@ -439,6 +439,7 @@ usr/lib/python?.?/site-packages/repomd usr/lib/python?.?/site-packages/pirut usr/lib/python?.?/site-packages/pykickstart usr/lib/python?.?/site-packages/rhpxl +usr/lib/python?.?/site-packages/bugzilla* usr/lib/python?.?/site-packages/cracklibmodule.so usr/lib/rpm/macros usr/lib/rpm/rpmpopt
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512
Chris Lumens wrote: | So, what does everyone think? Personally I'm a big fan of this because | it removes some back-and-forth on getting bug information and doesn't | require the user to have another computer set up and ready to scp things | to. |
I like this feature but please don't remove the Save to button. Imagine you're having a private network with no Internet access and hence no Bugzilla. It could be more easy/suitable to scp/USB key to Bugzilla than to connect the machine/network to the Internet.
Instead of removing the buttons entirely can we use a drop down menu or something else which is nice but keep all the available actions? That way we have the features but don't pollute the UI with multiple buttons.
Regards, Alexander.
On Mon, 2008-05-19 at 11:58 -0400, Chris Lumens wrote:
Here's an exciting and perhaps somewhat controversial patch set. This one removes support for saving bugs via scp and adds in its place support for saving directly to bugzilla. High points:
So this is something that we've talked about off and on for, umm, way too long to think about at this point. So it's probably something that we should think heavily about. But some questions that would also need to be answered
* bugUrl doesn't have to be a bugzilla instance. And are the XML-RPC methods being used even available at other bugzilla instances? There probably needs to be a way to say that this bug instance "supports" things filed in this manner. * Maybe this should be rolled in to the question of what to do about product.img * Keeping scp support seems to have some value, so I don't know that I'd replace it. Not sure what the right UI then is for choosing what to do with your traceback. Maybe something like the firefox download dialog [-----------------------------------] | Blah. We crashed. Some info. | | v Traceback details | | o Save to local disk | | o Send to bugzilla | | o Save to remote server | [-----------------------------------] * Should think hard about the right things to hash on. Especially given different products using the same/similar anaconda * It's a little scary to do this without a triager on the primary place we expect to be getting the bugs... :)
That's at least the first few minutes of thought. And I didn't really do more than give the code a glance at this point instead trying to think on some of the above.
Jeremy
So this is something that we've talked about off and on for, umm, way too long to think about at this point. So it's probably something that we should think heavily about. But some questions that would also need to be answered
Well now that we've got code, it's no longer a theoretical exercise. It helps that Will already wrote the hard part.
- bugUrl doesn't have to be a bugzilla instance. And are the XML-RPC
methods being used even available at other bugzilla instances? There probably needs to be a way to say that this bug instance "supports" things filed in this manner.
Unknown as to whether other bugzillas support the XML-RPC.
- Maybe this should be rolled in to the question of what to do about
product.img
Agreed. I'd kind of like to see all the product-related stuff disappear out of some dot-file in the tree and into whatever we do about product.img.
- Keeping scp support seems to have some value, so I don't know that I'd
replace it. Not sure what the right UI then is for choosing what to do with your traceback. Maybe something like the firefox download dialog [-----------------------------------] | Blah. We crashed. Some info. | | v Traceback details | | o Save to local disk | | o Send to bugzilla | | o Save to remote server | [-----------------------------------]
Sure, something like this is easy enough to do. Looks like there's enough support on the list for keeping it, even though I don't really care to. I always viewed it as a stop-gap until we got something better.
- Should think hard about the right things to hash on. Especially given
different products using the same/similar anaconda
Right now I'm taking the raw Python stack trace information and concatenating each stack frame's file, function, and code text together and then doing a sha256 on it. I skip the line number since those could change from build-to-build, and I skip the actual exception message ("RuntimeError: blah blah blah") because that could have information that changes from run-to-run, but is still the same traceback.
I want to make sure the hashing is right before committing any of this. Once bugs start getting filed through it, it'll be too late to change the hashing without missing dupes.
- It's a little scary to do this without a triager on the primary place
we expect to be getting the bugs... :)
Agreed. If the hashing's good it should mitigate things somewhat though. Also keep in mind you have to have a bugzilla account in order to file bugs so there's still that one hurdle between us and the masses.
- Chris
On Mon, May 19, 2008 at 01:23:12PM -0400, Chris Lumens wrote:
- bugUrl doesn't have to be a bugzilla instance. And are the XML-RPC
methods being used even available at other bugzilla instances? There probably needs to be a way to say that this bug instance "supports" things filed in this manner.
Unknown as to whether other bugzillas support the XML-RPC.
How about making this a bit more generic so that someone could write support for something other than Bugzilla? For instance rPath and Foresight use JIRA which supports both a SOAP and XML-RPC interface.
- Keeping scp support seems to have some value, so I don't know that I'd
replace it. Not sure what the right UI then is for choosing what to do with your traceback. Maybe something like the firefox download dialog [-----------------------------------] | Blah. We crashed. Some info. | | v Traceback details | | o Save to local disk | | o Send to bugzilla | | o Save to remote server | [-----------------------------------]
Sure, something like this is easy enough to do. Looks like there's enough support on the list for keeping it, even though I don't really care to. I always viewed it as a stop-gap until we got something better.
This could still be valuable from the point of view of debugging a dark site install.
Elliot
On Mon, 2008-05-19 at 14:34 -0400, Elliot Peele wrote:
On Mon, May 19, 2008 at 01:23:12PM -0400, Chris Lumens wrote:
- bugUrl doesn't have to be a bugzilla instance. And are the XML-RPC
methods being used even available at other bugzilla instances? There probably needs to be a way to say that this bug instance "supports" things filed in this manner.
Unknown as to whether other bugzillas support the XML-RPC.
How about making this a bit more generic so that someone could write support for something other than Bugzilla? For instance rPath and Foresight use JIRA which supports both a SOAP and XML-RPC interface.
Yeah, that's kind of what I was getting at :-)
Jeremy
Unknown as to whether other bugzillas support the XML-RPC.
How about making this a bit more generic so that someone could write support for something other than Bugzilla? For instance rPath and Foresight use JIRA which supports both a SOAP and XML-RPC interface.
Well sure, we could put some layer on top of python-bugzilla and then adapt anaconda to use that. Will, thoughts?
- Chris
On Mon, 2008-05-19 at 15:33 -0400, Chris Lumens wrote:
Unknown as to whether other bugzillas support the XML-RPC.
How about making this a bit more generic so that someone could write support for something other than Bugzilla? For instance rPath and Foresight use JIRA which supports both a SOAP and XML-RPC interface.
Well sure, we could put some layer on top of python-bugzilla and then adapt anaconda to use that. Will, thoughts?
Yeah, I don't see any reason you couldn't abstract this out and make python-bugzilla one of many possible bug reporting backends.
Writing a backend for JIRA is, of course, Someone Else's Problem.
-w
On Mon, May 19, 2008 at 03:33:08PM -0400, Chris Lumens wrote:
Unknown as to whether other bugzillas support the XML-RPC.
How about making this a bit more generic so that someone could write support for something other than Bugzilla? For instance rPath and Foresight use JIRA which supports both a SOAP and XML-RPC interface.
Well sure, we could put some layer on top of python-bugzilla and then adapt anaconda to use that. Will, thoughts?
I think, that all the hashing code should go into the "between" layer instead of Anaconda. Because we may consider changing that in the future (it just can happen) and some people will be reporting old bugs with even older releases (think RHEL with really old anaconda at that time).
With the detection code in anaconda it will be: 1) another possibility for errors, mistakes and typos 2) using older duplicate detection mechanism
And also adapting the "between" layer to new bugzilla API (which can happen too) will be much easier, than converting all ancient media someone has.
I think, that all the hashing code should go into the "between" layer instead of Anaconda. Because we may consider changing that in the future (it just can happen) and some people will be reporting old bugs with even older releases (think RHEL with really old anaconda at that time).
With the detection code in anaconda it will be:
- another possibility for errors, mistakes and typos
- using older duplicate detection mechanism
And also adapting the "between" layer to new bugzilla API (which can happen too) will be much easier, than converting all ancient media someone has.
Sure, we can do something like this. I'd take the approach of first getting in what I've done, then working on some intermediate layer to handle multiple bug handling systems, then adapting anaconda to it. If you look at the code in the patch, you'll see the amount that handles actually filing the bug is really small. Adapting later should be fairly straightforward.
It seems to me we need to spend a lot of time thinking about product.img and how things like this fit into it. Fun.
- Chris
- bugUrl doesn't have to be a bugzilla instance. And are the XML-RPC
methods being used even available at other bugzilla instances? There probably needs to be a way to say that this bug instance "supports" things filed in this manner.
- Maybe this should be rolled in to the question of what to do about
product.img
After talking with Will for a while, an idea has begun to form in my head. As we've already discussed elsewhere in this thread, different products use entirely different bug reporting systems. Therefore we probably need some sort of abstract layer on top of python-bugzilla that hides all the details.
This layer could define some base class that doesn't actually do anything. Then, the anaconda install classes could hold a reference to their bug reporting class. The default BaseInstallClass uses the useless base bug reporting class. Our more interesting install classes make use of the Bugzilla class. This holds all the information about it being a supported method, XML-RPC works, etc.
We could even toss the install class file in a product.img that sits alongside the stage2.img like we were discussing, if that makes things easier. I don't really know how to make this work with the tree composition scripts in anaconda, though. Ideas? Am I just going crazy?
- Keeping scp support seems to have some value, so I don't know that I'd
replace it. Not sure what the right UI then is for choosing what to do with your traceback. Maybe something like the firefox download dialog [-----------------------------------] | Blah. We crashed. Some info. | | v Traceback details | | o Save to local disk | | o Send to bugzilla | | o Save to remote server | [-----------------------------------]
By popular demand, this is done. I'll have another patch later once I finish my testing that the various save methods still work.
- Should think hard about the right things to hash on. Especially given
different products using the same/similar anaconda
Okay, what are your thoughts about what we should hash on? If you're concerned that various products are going to have the same bug and those end up getting duped when they should be separate, we can always add the productName into the hash.
I'd really like to get the hash value into its own field, but that's going to tie the code to a specific bugzilla instance. And then what do we do on all the other instances? It feels like we're going to need some very specific code to handle all these sorts of things.
- It's a little scary to do this without a triager on the primary place
we expect to be getting the bugs... :)
Ah but we have one now, at least for the summer!
- Chris
Chris Lumens wrote:
- bugUrl doesn't have to be a bugzilla instance. And are the XML-RPC
methods being used even available at other bugzilla instances? There probably needs to be a way to say that this bug instance "supports" things filed in this manner.
- Maybe this should be rolled in to the question of what to do about
product.img
After talking with Will for a while, an idea has begun to form in my head. As we've already discussed elsewhere in this thread, different products use entirely different bug reporting systems. Therefore we probably need some sort of abstract layer on top of python-bugzilla that hides all the details.
This layer could define some base class that doesn't actually do anything. Then, the anaconda install classes could hold a reference to their bug reporting class. The default BaseInstallClass uses the useless base bug reporting class. Our more interesting install classes make use of the Bugzilla class. This holds all the information about it being a supported method, XML-RPC works, etc.
We could even toss the install class file in a product.img that sits alongside the stage2.img like we were discussing, if that makes things easier. I don't really know how to make this work with the tree composition scripts in anaconda, though. Ideas? Am I just going crazy?
Doesn't sound entirely crazy. It'd be just like the package install backends now as the install backend gets chosen by the installclass. Maybe it does make sense to make these sorts of things more explicit, though, and specified via some other file rather than just being "figured out" through the install class. But that cna be a second step
- Should think hard about the right things to hash on. Especially given
different products using the same/similar anaconda
Okay, what are your thoughts about what we should hash on? If you're concerned that various products are going to have the same bug and those end up getting duped when they should be separate, we can always add the productName into the hash.
productName is the obvious one. I'm also not sure how relevant or not the different versions are. eg, should a Fedora 9 bug be a dupe of a Fedora 8 one? anaconda-11.4.0.87 vs anaconda-11.4.0.86 should almost certainly be a dupe though
Jeremy
Chris Lumens (clumens@redhat.com) said:
Here's an exciting and perhaps somewhat controversial patch set. This one removes support for saving bugs via scp and adds in its place support for saving directly to bugzilla. High points:
I like the idea of automatically filing bugs, but I also like being able to save the traceback for later, especially if I want to try and fix the traceback, or I'm working on a development version.
Maybe have 'save to...' only active based on betanag?
Bill
Chris Lumens wrote:
Here's an exciting and perhaps somewhat controversial patch set. This one removes support for saving bugs via scp and adds in its place support for saving directly to bugzilla. High points:
<snip>
So, what does everyone think? Personally I'm a big fan of this because it removes some back-and-forth on getting bug information and doesn't require the user to have another computer set up and ready to scp things to.
And if one doesn't have access to the Internet?
So, what does everyone think? Personally I'm a big fan of this because it removes some back-and-forth on getting bug information and doesn't require the user to have another computer set up and ready to scp things to.
And if one doesn't have access to the Internet?
Well there's still save to disk which should now even support USB since Joel patched around that problem.
- Chris
Chris Lumens wrote:
So, what does everyone think? Personally I'm a big fan of this because it removes some back-and-forth on getting bug information and doesn't require the user to have another computer set up and ready to scp things to.
And if one doesn't have access to the Internet?
Well there's still save to disk which should now even support USB since Joel patched around that problem.
scp across the LAN beats both (several of my machines don't have a floppy drive, and finding a disk can be challenging). USB disks are not yet the commodity items floppies were, or CD-Rs are.
An XML format and the means to submit them would be helpful, even when not reporting immediately: I really do not like BZ.
On Mon, May 19, 2008 at 19:51:15 -0400, Chris Lumens clumens@redhat.com wrote:
So, what does everyone think? Personally I'm a big fan of this because it removes some back-and-forth on getting bug information and doesn't require the user to have another computer set up and ready to scp things to.
And if one doesn't have access to the Internet?
Well there's still save to disk which should now even support USB since Joel patched around that problem.
Does it support encrypted usb drives?
Even so, unless there is significant support costs to allowing network saves, I think it would be a good idea to keep that capability.
anaconda-devel@lists.stg.fedoraproject.org