Merge lp:~jcsackett/launchpad/hardwaredb-should-ignore-bad-data into lp:launchpad

Proposed by j.c.sackett
Status: Merged
Approved by: Данило Шеган
Approved revision: no longer in the source branch.
Merged at revision: 12787
Proposed branch: lp:~jcsackett/launchpad/hardwaredb-should-ignore-bad-data
Merge into: lp:launchpad
Diff against target: 181 lines (+73/-12)
3 files modified
lib/lp/hardwaredb/scripts/hwdbsubmissions.py (+25/-10)
lib/lp/hardwaredb/tests/test_hwdb_submission_validation.py (+35/-2)
lib/lp/services/scripts/base.py (+13/-0)
To merge this branch: bzr merge lp:~jcsackett/launchpad/hardwaredb-should-ignore-bad-data
Reviewer Review Type Date Requested Status
Данило Шеган (community) Approve
Review via email: mp+56472@code.launchpad.net

Commit message

[r=danilo][bug=676988,747532] Disables oops creation for hardware processing errors introduced by bad data we do not control.

Description of the change

Summary
=======

Bad data sent to the HWDB caused an OOPS when it failed to validate. While we want to log the bad data as an error, we don't want it to generate an OOPS and get in the OOPS report, because there's nothing we can do about it beyond report bad data coming in from another source.

Preimplementation
=================

Spoke with Curtis Hovey, and explored the problem with him.

Implementation
==============
lib/lp/services/scripts/base.py
-------------------------------
Added a contextmanager for the OopsHandler that temporarily removes it from a logger, and adds it back in after the context expires.

lib/lp/hardwaredb/scripts/hwdbsubmissions.py
--------------------------------------------
Updated the self._logError method for the script to take a parameter indicating whether or not an Oops should be created. The contextmanager created in base.py is used to disable the Oopshandler if the parameter is false. It defaults to true.

lib/canonical/launchpad/scripts/tests/baddatahardwaretest.xml
lib/lp/hardwaredb/tests/test_hwdb_submission_validation.py
----------------------------------------------------------
Tests and test data.

Tests
=====

bin/test -vvct test_hwdb

QA
==

Create a hardware db entry on qastaging, staging, or launchpad.dev, using bad data (the data from the bug's linked oops report would be fine). Fire of the process-hwdb-submission job and confirm that no oops report is created for the bad data you provided.

Lint
====

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/canonical/launchpad/scripts/tests/baddatahardwaretest.xml
  lib/lp/hardwaredb/scripts/hwdbsubmissions.py
  lib/lp/hardwaredb/tests/test_hwdb_submission_validation.py
  lib/lp/services/scripts/base.py

To post a comment you must log in.
Revision history for this message
Данило Шеган (danilo) wrote :

As discussed on IRC, I dislike having a big XML file in the code with no clear indication what exactly is it about (other than being "bad" :), so you agreed: "in order, i'll try: modifying the data on the fly; cleaning everything possible out of the file; adding a big comment."

Regarding the XXX and cElementTree import in lib/lp/hardwaredb/scripts/hwdbsubmissions.py, JFDI is my suggestion.

Also, it'd be nice to have a minimal test for context-manager disable_oops_handler just so nobody breaks it for you.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/hardwaredb/scripts/hwdbsubmissions.py'
2--- lib/lp/hardwaredb/scripts/hwdbsubmissions.py 2010-08-24 10:36:30 +0000
3+++ lib/lp/hardwaredb/scripts/hwdbsubmissions.py 2011-04-08 19:03:55 +0000
4@@ -13,11 +13,11 @@
5 'process_pending_submissions',
6 ]
7
8-
9 import bz2
10 from cStringIO import StringIO
11
12-
13+#XXX: Given the version of python we know we're running on now,
14+# is the try/except here necessary?
15 try:
16 import xml.etree.cElementTree as etree
17 except ImportError:
18@@ -39,14 +39,24 @@
19 from canonical.config import config
20 from canonical.librarian.interfaces import LibrarianServerError
21 from lp.hardwaredb.interfaces.hwdb import (
22- HWBus, HWSubmissionProcessingStatus, IHWDeviceDriverLinkSet, IHWDeviceSet,
23- IHWDriverSet, IHWSubmissionDeviceSet, IHWSubmissionSet, IHWVendorIDSet,
24- IHWVendorNameSet)
25+ HWBus,
26+ HWSubmissionProcessingStatus,
27+ IHWDeviceDriverLinkSet,
28+ IHWDeviceSet,
29+ IHWDriverSet,
30+ IHWSubmissionDeviceSet,
31+ IHWSubmissionSet,
32+ IHWVendorIDSet,
33+ IHWVendorNameSet,
34+ )
35 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
36 from canonical.launchpad.interfaces.looptuner import ITunableLoop
37 from canonical.launchpad.utilities.looptuner import LoopTuner
38 from canonical.launchpad.webapp.errorlog import (
39- ErrorReportingUtility, ScriptRequest)
40+ ErrorReportingUtility,
41+ ScriptRequest,
42+ )
43+from lp.services.scripts.base import disable_oops_handler
44
45 _relax_ng_files = {
46 '1.0': 'hardware-1_0.rng', }
47@@ -126,10 +136,14 @@
48 self._setHardwareSectionParsers()
49 self._setSoftwareSectionParsers()
50
51- def _logError(self, message, submission_key):
52+ def _logError(self, message, submission_key, create_oops=True):
53 """Log `message` for an error in submission submission_key`."""
54- self.logger.error(
55- 'Parsing submission %s: %s' % (submission_key, message))
56+ msg = 'Parsing submission %s: %s' % (submission_key, message)
57+ if not create_oops:
58+ with disable_oops_handler(self.logger):
59+ self.logger.error(msg)
60+ else:
61+ self.logger.error(msg)
62
63 def _logWarning(self, message, warning_id=None):
64 """Log `message` for a warning in submission submission_key`."""
65@@ -172,7 +186,8 @@
66 if not validator.validate(submission):
67 self._logError(
68 'Relax NG validation failed.\n%s' % validator.error_log,
69- submission_key)
70+ submission_key,
71+ create_oops=False)
72 return None
73 return submission_doc
74
75
76=== removed directory 'lib/lp/hardwaredb/scripts/tests'
77=== renamed file 'lib/lp/hardwaredb/scripts/tests/real_hwdb_submission.xml.bz2' => 'lib/lp/hardwaredb/tests/real_hwdb_submission.xml.bz2'
78=== renamed file 'lib/lp/hardwaredb/scripts/tests/test_hwdb_submission_validation.py' => 'lib/lp/hardwaredb/tests/test_hwdb_submission_validation.py'
79--- lib/lp/hardwaredb/scripts/tests/test_hwdb_submission_validation.py 2010-10-04 19:50:45 +0000
80+++ lib/lp/hardwaredb/tests/test_hwdb_submission_validation.py 2011-04-08 19:03:55 +0000
81@@ -11,10 +11,11 @@
82 TestCase,
83 TestLoader,
84 )
85-
86 from zope.testing.loghandler import Handler
87
88 from canonical.config import config
89+from canonical.launchpad.webapp.errorlog import globalErrorUtility
90+from canonical.launchpad.scripts.logger import OopsHandler
91 from canonical.testing.layers import BaseLayer
92 from lp.hardwaredb.scripts.hwdbsubmissions import SubmissionParser
93
94@@ -56,13 +57,15 @@
95 submission_id)
96 return result, submission_id
97
98- def insertSampledata(self, data, insert_text, where):
99+ def insertSampledata(self, data, insert_text, where, after=False):
100 """Insert text into the sample data `data`.
101
102 Insert the text `insert_text` before the first occurrence of
103 `where` in `data`.
104 """
105 insert_position = data.find(where)
106+ if after:
107+ insert_position += len(where)
108 return data[:insert_position] + insert_text + data[insert_position:]
109
110 def replaceSampledata(self, data, replace_text, from_text, to_text):
111@@ -99,6 +102,36 @@
112 self.assertEqual(result, None,
113 'Invalid root node not detected')
114
115+ def _getLastOopsTime(self):
116+ try:
117+ last_oops_time = globalErrorUtility.getLastOopsReport().time
118+ except AttributeError:
119+ # There haven't been any oopses in this test run
120+ last_oops_time = None
121+ return last_oops_time
122+
123+ def test_bad_data_does_not_oops(self):
124+ # If the processing cronscript gets bad data, it should log it, but
125+ # it should not create an Oops.
126+ sample_data = self.insertSampledata(
127+ data=self.sample_data,
128+ insert_text=('<dmi>'
129+ '/sys/class/dmi/id/bios_vendor:Dell Inc.'
130+ '/sys/class/dmi/id/bios_version:A12'
131+ '</dmi>'),
132+ where = '<hardware>',
133+ after=True)
134+ # Add the OopsHandler to the log, because we want to make sure this
135+ # doesn't create an Oops report.
136+ logging.getLogger('test_hwdb_submission_parser').addHandler(OopsHandler(self.log.name))
137+ result, submission_id = self.runValidator(sample_data)
138+ last_oops_time = self._getLastOopsTime()
139+ # We use the class method here, because it's been overrided for the
140+ # other tests in this test case.
141+ TestCase.assertEqual(self,
142+ self._getLastOopsTime(),
143+ last_oops_time)
144+
145 def testInvalidFormatVersion(self):
146 """The attribute `format` of the root node must be `1.0`."""
147 sample_data = '<?xml version="1.0" ?><system version="nonsense"/>'
148
149=== modified file 'lib/lp/services/scripts/base.py'
150--- lib/lp/services/scripts/base.py 2011-03-30 16:39:06 +0000
151+++ lib/lp/services/scripts/base.py 2011-04-08 19:03:55 +0000
152@@ -6,9 +6,11 @@
153 'LaunchpadCronScript',
154 'LaunchpadScript',
155 'LaunchpadScriptFailure',
156+ 'disable_oops_handler',
157 'SilentLaunchpadScriptFailure',
158 ]
159
160+from contextlib import contextmanager
161 from ConfigParser import SafeConfigParser
162 from cProfile import Profile
163 import datetime
164@@ -401,6 +403,17 @@
165 date_completed=date_completed)
166 self.txn.commit()
167
168+@contextmanager
169+def disable_oops_handler(logger):
170+ oops_handlers = []
171+ for handler in logger.handlers:
172+ if isinstance(handler, OopsHandler):
173+ oops_handlers.append(handler)
174+ logger.removeHandler(handler)
175+ yield
176+ for handler in oops_handlers:
177+ logger.addHandler(handler)
178+
179
180 def cronscript_enabled(control_url, name, log):
181 """Return True if the cronscript is enabled."""