Merge lp:~sinzui/charmworld/stale-metadata into lp:~juju-jitsu/charmworld/trunk

Proposed by Curtis Hovey
Status: Merged
Approved by: Brad Crittenden
Approved revision: 313
Merged at revision: 310
Proposed branch: lp:~sinzui/charmworld/stale-metadata
Merge into: lp:~juju-jitsu/charmworld/trunk
Diff against target: 112 lines (+34/-17)
3 files modified
charmworld/jobs/ingest.py (+7/-6)
charmworld/jobs/tests/test_scan.py (+26/-9)
charmworld/testing/factory.py (+1/-2)
To merge this branch: bzr merge lp:~sinzui/charmworld/stale-metadata
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+175126@code.launchpad.net

Commit message

Always update the historic charm data with the new metadata and config data.

Description of the change

RULES

    Pre-implementation: no one
    * New metadata like requires and provides are not appearing in
      manage.jujucharms.com. First time ingests show the correct data.
    * The scan_charm function gathers the new metadata (and config data)
      as dict, then wrongly updates the dict with the historic charm data.
      The assumption was that the merger of the two dicts was safe because
      they never share the same keys.
      * This has not been true for several weeks. A change to the ingest
        process always starts with the historic charm data to ensure
        failures during ingestion have some sane data to save.
      * The simple fix is to reverse the update of the dicts, so that the
        new data overwrites the old data.

    ADDENDUM
    * Aaron remarked that process_charm() is mutating the charm_data, but
      we want to instead use the returned dict. We want to work with a
      copy of the data to minimise the mutations to the dict --be safe.
      * Update process_charm() to work with a copy of the charm_data
      * Update callsites to always use the returned charm_data.

QA

    * Wait for ingestion to run on staging.
    * Verify that the mysql charm requires 3 interfaces instead of 1.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

This change looks good.

I was initially concerned that the copy in 'process_charm' was not a deep copy given that the charm is a dict of dicts. But looking at the code, no lower-level dicts are mutated so it should be fine.

review: Approve (code)

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-07-11 10:53:45 +0000
3+++ charmworld/jobs/ingest.py 2013-07-16 20:31:24 +0000
4@@ -526,12 +526,13 @@
5 hooks.sort()
6 metadata["hooks"] = hooks
7
8- # Stuff into the db
9- metadata.update(charm_data)
10- metadata = process_charm(metadata)
11+ # Update the historic or incomplete charm_data with the new metadata.
12+ # Stuff into the db.
13+ charm_data.update(metadata)
14+ complete_charm_data = process_charm(charm_data)
15 # Modify charm_data itself.
16 charm_data.clear()
17- charm_data.update(metadata)
18+ charm_data.update(complete_charm_data)
19
20
21 def normalize_interfaces(interfaces):
22@@ -551,9 +552,9 @@
23 interfaces[relation_name] = {'interface': relation_data}
24
25
26-def process_charm(charm):
27+def process_charm(base_charm):
28 # Enrich charm metadata for webapp.
29-
30+ charm = dict(base_charm)
31 # Charm url
32 if charm["promulgated"]:
33 charm["short_url"] = "/charms/%s/%s" % (
34
35=== modified file 'charmworld/jobs/tests/test_scan.py'
36--- charmworld/jobs/tests/test_scan.py 2013-07-11 16:11:40 +0000
37+++ charmworld/jobs/tests/test_scan.py 2013-07-16 20:31:24 +0000
38@@ -101,6 +101,23 @@
39 }
40 self.assertEqual(expected, self.charm['provides'])
41
42+ def test_short_interface_specification_reingest(self):
43+ # Reingestion of changes will update the charm in the db.
44+ self.charm['provides'] = {'api': {'interface': 'rest'}}
45+ self.charm['requires'] = {'database': {'interface': 'mysql'}}
46+ scan_charm(self.charm, self.db, self.fs, self.log)
47+ # The sample charm's metadata uses this information.
48+ p_expected = {
49+ 'ssl_website': {'interface': 'https'},
50+ 'website': {'interface': 'http'},
51+ }
52+ r_expected = {
53+ 'database': {'interface': 'mongodb'},
54+ 'sql_db': {'interface': 'pgsql'},
55+ }
56+ self.assertEqual(r_expected, self.charm['requires'])
57+ self.assertEqual(p_expected, self.charm['provides'])
58+
59 def test_invalid_metadata_raises_IngestError(self):
60 metadata = 'invalid_syntax: ['
61 metadata_file = factory.make_charm_file(
62@@ -136,10 +153,10 @@
63 'name': 'bar',
64 'promulgated': False,
65 }
66- process_charm(charm)
67- self.assertEqual('/~foo/precise/bar', charm['short_url'])
68+ new_charm = process_charm(charm)
69+ self.assertEqual('/~foo/precise/bar', new_charm['short_url'])
70 # The label of unpromulgated charms starts with the owner name.
71- self.assertEqual('~foo:precise/bar', charm['label'])
72+ self.assertEqual('~foo:precise/bar', new_charm['label'])
73 # Promulgated charms do not have the owner name in the short URL
74 # and in the label.
75 charm = {
76@@ -148,9 +165,9 @@
77 'name': 'bar',
78 'promulgated': True,
79 }
80- process_charm(charm)
81- self.assertEqual('/charms/precise/bar', charm['short_url'])
82- self.assertEqual('precise/bar', charm['label'])
83+ new_charm = process_charm(charm)
84+ self.assertEqual('/charms/precise/bar', new_charm['short_url'])
85+ self.assertEqual('precise/bar', new_charm['label'])
86 # This is also true if the charm is not owned by charmes.
87 charm = {
88 'owner': 'foo',
89@@ -158,6 +175,6 @@
90 'name': 'bar',
91 'promulgated': True,
92 }
93- process_charm(charm)
94- self.assertEqual('/charms/precise/bar', charm['short_url'])
95- self.assertEqual('precise/bar', charm['label'])
96+ new_charm = process_charm(charm)
97+ self.assertEqual('/charms/precise/bar', new_charm['short_url'])
98+ self.assertEqual('precise/bar', new_charm['label'])
99
100=== modified file 'charmworld/testing/factory.py'
101--- charmworld/testing/factory.py 2013-07-12 14:53:18 +0000
102+++ charmworld/testing/factory.py 2013-07-16 20:31:24 +0000
103@@ -287,8 +287,7 @@
104 charm['name'], charm['branch_spec'], status, provider)
105 if subordinate:
106 charm['subordinate'] = True
107- process_charm(charm)
108- return charm
109+ return process_charm(charm)
110
111
112 def makeCharm(db, *args, **kwargs):

Subscribers

People subscribed via source and target branches