Merge lp:~adeuring/charmworld/better-handling-of-invalid-metadata-1169662 into lp:~juju-jitsu/charmworld/trunk

Proposed by Abel Deuring
Status: Merged
Approved by: Richard Harding
Approved revision: 214
Merged at revision: 215
Proposed branch: lp:~adeuring/charmworld/better-handling-of-invalid-metadata-1169662
Merge into: lp:~juju-jitsu/charmworld/trunk
Diff against target: 229 lines (+135/-12)
4 files modified
charmworld/jobs/ingest.py (+22/-10)
charmworld/jobs/tests/test_ingest.py (+82/-0)
charmworld/jobs/tests/test_scan.py (+27/-0)
charmworld/testing/factory.py (+4/-2)
To merge this branch: bzr merge lp:~adeuring/charmworld/better-handling-of-invalid-metadata-1169662
Reviewer Review Type Date Requested Status
Richard Harding (community) Approve
Review via email: mp+161109@code.launchpad.net

Commit message

ingest: stop processing charms with invalid metadata or config data.

Description of the change

This branch fixes the bugs 1169662 (UnboundLocalError: local variable
'metadata' referenced before assignment) and 1169660 (UnboundLocalError:
local variable 'config' referenced before assignment).

The cause of the bugs is simple: The ingest script just tried to
continue, even if the files metadata.yaml or config.yaml are invalid.

ScanJob now raises the new exception IngestError if these files cannot
be parsed; I changed the function run_job() to log the new exception
at level "info".

I also added a few trivial tests for run_job().

To post a comment you must log in.
Revision history for this message
Richard Harding (rharding) wrote :

thanks

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmworld/jobs/ingest.py'
2--- charmworld/jobs/ingest.py 2013-04-25 16:44:55 +0000
3+++ charmworld/jobs/ingest.py 2013-04-26 13:00:39 +0000
4@@ -58,6 +58,10 @@
5 "%(series)s-%(provider)s-charm-%(charm)s/lastBuild/api/json")
6
7
8+class IngestError(Exception):
9+ pass
10+
11+
12 class IngestJob(object):
13
14 name = 'default'
15@@ -190,6 +194,17 @@
16 self.update_charm(charm_data, branch_dir, retry)
17
18
19+def log(stage, level, exc, charm_data):
20+ logger = logging.getLogger("charm.%s" % stage)
21+ err_msg = "%s error on %s: %s" % (
22+ stage, charm_data, str(exc))
23+ logger.log(level, err_msg)
24+ charm_error = {'error_stage': stage, 'error': str(exc)}
25+ if hasattr(exc, 'output'):
26+ charm_error['output'] = str(exc.output)
27+ charm_data['error'] = charm_error
28+
29+
30 def run_job(job, charm_data, needs_setup=True, db=None):
31 if needs_setup:
32 if db is not None:
33@@ -197,17 +212,13 @@
34 else:
35 job.setup()
36 stage = job.name
37- log = logging.getLogger("charm.%s" % stage)
38 try:
39 job.run(charm_data)
40+ except IngestError as e:
41+ log(stage, logging.INFO, e, charm_data)
42+ return False
43 except Exception as e:
44- err_msg = "%s error on %s: %s" % (
45- stage, charm_data, str(e))
46- log.exception(err_msg)
47- charm_error = {'error_stage': stage, 'error': str(e)}
48- if hasattr(e, 'output'):
49- charm_error['output'] = str(e.output)
50- charm_data['error'] = charm_error
51+ log(stage, logging.ERROR, e, charm_data)
52 return False
53 return True
54
55@@ -577,7 +588,8 @@
56 try:
57 metadata = quote_yaml(yaml.load(cfile.read()))
58 except Exception, exc:
59- self.log.info('Invalid charm metadata %s: %s' % (
60+ raise IngestError(
61+ 'Invalid charm metadata %s: %s' % (
62 charm_data['branch_spec'],
63 exc)
64 )
65@@ -590,7 +602,7 @@
66 try:
67 config = quote_yaml(yaml.load(config_raw))
68 except Exception, exc:
69- self.log.info(
70+ raise IngestError(
71 'Invalid charm config yaml. %s: %s' % (
72 charm_data['branch_spec'],
73 exc)
74
75=== modified file 'charmworld/jobs/tests/test_ingest.py'
76--- charmworld/jobs/tests/test_ingest.py 2013-04-24 20:31:51 +0000
77+++ charmworld/jobs/tests/test_ingest.py 2013-04-26 13:00:39 +0000
78@@ -19,6 +19,8 @@
79
80 from charmworld.charmstore import get_address
81 from charmworld.jobs.ingest import (
82+ IngestError,
83+ IngestJob,
84 run_job,
85 update_charm,
86 UpdateCharmJob,
87@@ -171,3 +173,83 @@
88 with update_environment(charm_data, self.use_index_client()):
89 update_charm(charm_data, self.db)
90 self.assertIs(None, self.db.charms.find_one(charm_data['_id']))
91+
92+
93+class TestJob(IngestJob):
94+ name = 'test'
95+
96+ def __init__(self, exc=None):
97+ super(TestJob, self).setup()
98+ self.setup_called = False
99+ self.run_called = False
100+ self.exc = exc
101+ self.charm_data = None
102+
103+ def setup(self, db=None):
104+ self.setup_called = True
105+ self.db = db
106+
107+ def run(self, charm_data):
108+ self.charm_data = charm_data
109+ if self.exc is not None:
110+ raise self.exc('test error')
111+ self.run_called = True
112+
113+
114+class TestRunJob(TestCase):
115+
116+ def test_run_job_defaults(self):
117+ job = TestJob()
118+ result = run_job(job, charm_data='foo')
119+ self.assertTrue(result)
120+ self.assertTrue(job.setup_called)
121+ self.assertTrue(job.run_called)
122+ self.assertEqual('foo', job.charm_data)
123+ self.assertIs(None, job.db)
124+
125+ def test_run_job_no_setup(self):
126+ job = TestJob()
127+ run_job(job, 'foo', needs_setup=False)
128+ self.assertFalse(job.setup_called)
129+
130+ def test_run_job_db_specified(self):
131+ job = TestJob()
132+ run_job(job, 'foo', db='db')
133+ self.assertEqual('db', job.db)
134+ job = TestJob()
135+ run_job(job, 'foo', needs_setup=False, db='db')
136+ self.assertIs(None, getattr(job, 'db', None))
137+
138+ def test_run_job_general_exception(self):
139+ handler = self.get_handler('charm.test')
140+ job = TestJob(ValueError)
141+ charm_data = {'foo': 'bar'}
142+ result = run_job(job, charm_data)
143+ self.assertFalse(result)
144+ expected_error = {
145+ 'error': 'test error',
146+ 'error_stage': 'test',
147+ }
148+ self.assertEqual(expected_error, charm_data['error'])
149+ log_messages = [record.getMessage() for record in handler.buffer]
150+ expected = ["test error on {'foo': 'bar'}: test error"]
151+ self.assertEqual(expected, log_messages)
152+ log_level = handler.buffer[0].levelname
153+ self.assertEqual('ERROR', log_level)
154+
155+ def test_run_job_IngestError(self):
156+ handler = self.get_handler('charm.test')
157+ job = TestJob(IngestError)
158+ charm_data = {'foo': 'bar'}
159+ result = run_job(job, charm_data)
160+ self.assertFalse(result)
161+ expected_error = {
162+ 'error': 'test error',
163+ 'error_stage': 'test',
164+ }
165+ self.assertEqual(expected_error, charm_data['error'])
166+ log_messages = [record.getMessage() for record in handler.buffer]
167+ expected = ["test error on {'foo': 'bar'}: test error"]
168+ self.assertEqual(expected, log_messages)
169+ log_level = handler.buffer[0].levelname
170+ self.assertEqual('INFO', log_level)
171
172=== modified file 'charmworld/jobs/tests/test_scan.py'
173--- charmworld/jobs/tests/test_scan.py 2013-04-25 18:08:08 +0000
174+++ charmworld/jobs/tests/test_scan.py 2013-04-26 13:00:39 +0000
175@@ -2,6 +2,7 @@
176 # GNU Affero General Public License version 3 (see the file LICENSE).
177
178 from charmworld.jobs.ingest import BzrIngestJob
179+from charmworld.jobs.ingest import IngestError
180 from charmworld.jobs.ingest import ScanIngestJob
181 from charmworld.testing import factory
182 from charmworld.testing import JobTestBase
183@@ -61,3 +62,29 @@
184 'website': {'interface': 'http'},
185 }
186 self.assertEqual(expected, self.charm['provides'])
187+
188+ def test_invalid_metadata_raises_IngestError(self):
189+ metadata = 'invalid_syntax: ['
190+ metadata_file = factory.make_charm_file(
191+ self.db, self.charm, '/path/to/metadata.yaml', content=metadata)
192+ self.charm['files'][quote_key('metadata.yaml')] = {
193+ 'fileid': metadata_file._id
194+ }
195+ with self.assertRaises(IngestError) as ctx:
196+ self.scan_job.run(self.charm)
197+ info = str(ctx.exception)
198+ self.assertTrue(info.startswith(
199+ 'Invalid charm metadata ~charmers/charms/precise/sample'))
200+
201+ def test_invalid_config_yaml_raises_IngestError(self):
202+ config = 'invalid_syntax: ['
203+ config_file = factory.make_charm_file(
204+ self.db, self.charm, '/path/to/config.yaml', content=config)
205+ self.charm['files'][quote_key('config.yaml')] = {
206+ 'fileid': config_file._id
207+ }
208+ with self.assertRaises(IngestError) as ctx:
209+ self.scan_job.run(self.charm)
210+ info = str(ctx.exception)
211+ self.assertTrue(info.startswith(
212+ 'Invalid charm config yaml. ~charmers/charms/precise/sample'))
213
214=== modified file 'charmworld/testing/factory.py'
215--- charmworld/testing/factory.py 2013-04-18 20:11:19 +0000
216+++ charmworld/testing/factory.py 2013-04-26 13:00:39 +0000
217@@ -271,10 +271,12 @@
218 return record_id, charm
219
220
221-def make_charm_file(db, charm, path):
222+def make_charm_file(db, charm, path, content=None):
223 fs = getfs(db)
224 charm_file = CharmFileSet.make_charm_file(fs, charm, path, basename(path))
225- charm_file.save(random_string(10).encode('utf-8'))
226+ if content is None:
227+ content = random_string(10).encode('utf-8')
228+ charm_file.save(content)
229 return charm_file
230
231

Subscribers

People subscribed via source and target branches