Merge lp:~mvo/pkgme-devportal/more-architectures into lp:pkgme-devportal

Proposed by Michael Vogt
Status: Merged
Approved by: James Westby
Approved revision: 66
Merged at revision: 88
Proposed branch: lp:~mvo/pkgme-devportal/more-architectures
Merge into: lp:pkgme-devportal
Prerequisite: lp:~mvo/pkgme-devportal/raise-on-unsupported-architectures
Diff against target: 225 lines (+35/-30)
5 files modified
acceptance/tests/__init__.py (+3/-3)
devportalbinary/binary.py (+3/-1)
devportalbinary/database.py (+15/-12)
devportalbinary/tests/test_binary.py (+4/-4)
devportalbinary/tests/test_database.py (+10/-10)
To merge this branch: bzr merge lp:~mvo/pkgme-devportal/more-architectures
Reviewer Review Type Date Requested Status
James Westby Approve
Review via email: mp+116224@code.launchpad.net

Description of the change

This branch adds a architecture field to the database and make the LP code add this architecture field when scanning.

This branch lacks database migration, so it will require the old db to be destroyed and a new one created unfortunately.

To post a comment you must log in.
Revision history for this message
James Westby (james-w) wrote :

Hi Michael,

This looks sensible. It may be that we want a different schema for best querying,
but at this point I don't think so, and we'd need more information to know. Therefore
I think this is the right approach at this point.

Thanks,

James

65. By Michael Vogt

merged updated lp:~mvo/pkgme-devportal/raise-on-unsupported-architectures

66. By Michael Vogt

update update_source_package() for the new api

67. By Michael Vogt

merged fix from lp:~mvo/pkgme-devportal/raise-on-unsupported-architectures

Revision history for this message
James Westby (james-w) wrote :

Hi,

We'll want to add architecture to the query interface of libdep-service
at some point, but I was wrong that this will break libdep-service I think,
so approving.

Thanks,

James

review: Approve
Revision history for this message
James Westby (james-w) wrote :

Oh, but we do need a schema migration to deploy this. We don't have a
framework for that yet.

We can probably do something cheap for this one if we have the SQL needed
to do the migration.

Thanks,

James

68. By Michael Vogt

merged from trunk to fix tests

Revision history for this message
James Westby (james-w) wrote :

On Tue, 24 Jul 2012 16:01:18 -0000, James Westby <email address hidden> wrote:
> Oh, but we do need a schema migration to deploy this. We don't have a
> framework for that yet.
>
> We can probably do something cheap for this one if we have the SQL needed
> to do the migration.

To answer your question on IRC, it doesn't have to be a migration
per-se, but we can't rollout until we know how it is handled as all the
queries will break if they don't match the db, so we need an answer
before we can land this unfortunately.

Thanks,

James

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks James, so I assume we want something like the following sql:

"""
ALTER TABLE libdep ADD architecture TEXT;
UPDATE libdep SET architecture = "i386";
"""

to be run on the DB when this gets deployed. Would this be something we ask the webops to do ad-hoc?
Or something like https://lists.ubuntu.com/archives/storm/2009-August/001117.html ? Or something else?

Revision history for this message
Michael Vogt (mvo) wrote :

Hm, apparently storm has some native support for this too, I look at this now.

Revision history for this message
Michael Vogt (mvo) wrote :

Some info on this is here:
http://people.canonical.com/~therve/storm/storm.schema.schema.Schema.html

but its not very well documented and it appears that it will not help with this migration as the DB itself needs to be created with the storm.schema stuff so that it has the "patch" table.

Revision history for this message
Michael Vogt (mvo) wrote :

I put the storm version of this migration code into lp:~mvo/pkgme-devportal/schemas-via-storm now.

Revision history for this message
James Westby (james-w) wrote :

Hi,

After discussing this more, the mechanism of the schema update isn't the important
thing, it's having the code deal with the fact that we won't do the migration
as well roll it out.

The steps would be

1) Land a change to cope with the architecture column being present or not:
   - if it's there it should insert data in to it
   - if it's there it can use it in queries, if it assumes that NULL == 'i386', or it can not use it in queries.
   - it can add the column to the CREATE TABLE IF NOT EXIST statement, as that won't trigger when pushing out the code.

2) Roll that change out

3) Run SQL to ALTER TABLE ADD COLUMN architecture;

3) Run background job to UPDATE SET architecture = 'i386'; This may want to be batched.

4) Once the job completes ALTER COLUMN ADD CONSTRAINT NOT NULL;

5) The db is now ready, once this is done on production land a change to use the column in queries, expecting it to be there and with no nulls and 'i386' for all current data.

6) Roll that change out.

So, some changes are needed to this branch:

72 + "architecture TEXT, "

Should add a NOT NULL constraint I think, we expect every row to have a non-NULL architecture
eventually.

93 + result = self._store.execute("SELECT dependency FROM libdep WHERE library = ? AND architecture = ?",
94 + (unicode(library_name), unicode(arch)))

It shouldn't change that currently, it should just assume that architecture == 'i386' for all rows for now.

108 + self._store.execute("INSERT INTO libdep VALUES (?, ?, ?, ?)",
109 + (unicode(source_package_name), unicode(library_name), unicode(dependency), unicode(arch)))

That needs to change the INSERT statement depending on whether the architecture column is present or not.

With those changes we can get this landed, rolled out to staging, and start the migration there.

Two things come to mind:

  * A way to test if a column is part of a table will be important to have. jml has written code for this before I think.
  * We may want something like dblooptuner for doing the background migration.

Thanks,

James

Revision history for this message
James Westby (james-w) wrote :

lifeless corrected my process:

<lifeless> so for a given refactoring, like 'add a required column', you build that out of those two primitives:
<lifeless> - add a nullable column
<lifeless> - add a backfill job and make your other code populate the column, and handle it being null
<lifeless> - wait for the backfill
<lifeless> - change schema to make the column NOT NULL
<lifeless> done.

Thanks,

James

Revision history for this message
James Westby (james-w) wrote :

Hi,

So we need a step before those because we currently do

  "INSERT INTO libdep VALUES (?, ?, ?)"

which won't work with multiple columns I don't think (though
I haven't tested.) That means the first step would be to land
a change that altered that.

I don't want to block you on all of this though Michael, so I'd
be +1 on going ahead with a managed rollout of this change
with lockstep db migration, and to start to work on these
issues so we can do better with the next change.

Thanks,

James

Revision history for this message
James Westby (james-w) wrote :

Oh, and I've just realised that I think architecture must be part of the
unique constraint, otherwise it will fail if the same package has the same
library and dependencies on multiple architectures.

Thanks,

James

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'acceptance/tests/__init__.py'
2--- acceptance/tests/__init__.py 2012-07-17 12:34:24 +0000
3+++ acceptance/tests/__init__.py 2012-07-24 17:18:20 +0000
4@@ -53,8 +53,8 @@
5 def test_gtk(self):
6 """Runs successfully for a basic GTK+ application."""
7 dep_db = self.useFixture(DatabaseFixture())
8- dep_db.db.update_source_package("pthreads", [[("libpthread.so.0", "libpthread0")]])
9- dep_db.db.update_source_package("eglibc", [[("libc.so.6", "libc6")]])
10+ dep_db.db.update_source_package("pthreads", [[("libpthread.so.0", "libpthread0", "i386")]])
11+ dep_db.db.update_source_package("eglibc", [[("libc.so.6", "libc6", "i386")]])
12 test_data = self.useFixture(TestData("gtk"))
13 self.run_pkgme(test_data)
14 self.assertThat(
15@@ -63,7 +63,7 @@
16 def test_bundled_library(self):
17 """Runs successfully for a basic bundled libary."""
18 dep_db = self.useFixture(DatabaseFixture())
19- dep_db.db.update_source_package("eglibc", [[("libc.so.6", "libc6")]])
20+ dep_db.db.update_source_package("eglibc", [[("libc.so.6", "libc6", "i386")]])
21 test_data = self.useFixture(TestData("bundled_lib"))
22 self.run_pkgme(test_data)
23 self.assertThat(
24
25=== modified file 'devportalbinary/binary.py'
26--- devportalbinary/binary.py 2012-07-24 17:08:59 +0000
27+++ devportalbinary/binary.py 2012-07-24 17:18:20 +0000
28@@ -138,6 +138,7 @@
29 cmd = [OBJDUMP, '-f', '-p'] + binary_paths
30 output = run_subprocess(cmd)
31 libraries = {}
32+ architecture = None
33 last_line_was_blank = True
34 for line in output:
35 this_line_blank = (line.strip() == '')
36@@ -148,7 +149,8 @@
37 break
38 libraries[current_binary] = []
39 if line.startswith(' NEEDED'):
40- libraries[current_binary].append(line.split()[1])
41+ soname = line.split()[1]
42+ libraries[current_binary].append(soname)
43 elif line.startswith('architecture: '):
44 # so... i386 is reported as "i386"
45 # *but* amd64 as "i386:x86-64"
46
47=== modified file 'devportalbinary/database.py'
48--- devportalbinary/database.py 2012-07-19 17:29:48 +0000
49+++ devportalbinary/database.py 2012-07-24 17:18:20 +0000
50@@ -131,7 +131,7 @@
51 def iter_symbols(self):
52 contents = self.get_symbol_contents()
53 if contents:
54- yield contents
55+ yield contents, self.bpph.distro_arch_series.architecture_tag
56
57
58 class SourcePackage(object):
59@@ -180,7 +180,7 @@
60 path = download_file(url, directory)
61 symbols_contents = extract_symbols_file(path)
62 if symbols_contents:
63- yield symbols_contents
64+ yield symbols_contents, architecture
65 shutil.rmtree(directory)
66
67
68@@ -324,6 +324,7 @@
69 "source_package_name TEXT, "
70 "library TEXT, "
71 "dependency TEXT, "
72+ "architecture TEXT, "
73 "CONSTRAINT libdep_uniq UNIQUE (source_package_name, "
74 "library, dependency))")
75 elif store_type == 'postgres':
76@@ -337,6 +338,7 @@
77 "source_package_name TEXT, "
78 "library TEXT, "
79 "dependency TEXT, "
80+ "architecture TEXT, "
81 "CONSTRAINT libdep_uniq UNIQUE (source_package_name, "
82 "library, dependency))")
83 else:
84@@ -344,21 +346,22 @@
85 store.commit()
86 return cls(store)
87
88- def get_dependencies(self, library_name):
89+ def get_dependencies(self, library_name, arch='i386'):
90 """Get the binary packagaes that provide 'library_name'."""
91- result = self._store.execute("SELECT dependency FROM libdep WHERE library = ?",
92- (unicode(library_name),))
93+ result = self._store.execute("SELECT dependency FROM libdep WHERE library = ? AND architecture = ?",
94+ (unicode(library_name), unicode(arch)))
95 return set([dependency for [dependency] in result])
96
97- def insert_new_library(self, source_package_name, library_name, dependency):
98+ def insert_new_library(self, source_package_name, library_name,
99+ dependency, arch):
100 """Insert a library and its needed dependency into the database.
101
102 :param library_name: A full soname, e.g. libfoo.so.1.
103 :param dependency: A binary package dependency, possibly including
104 version.
105 """
106- self._store.execute("INSERT INTO libdep VALUES (?, ?, ?)",
107- (unicode(source_package_name), unicode(library_name), unicode(dependency)))
108+ self._store.execute("INSERT INTO libdep VALUES (?, ?, ?, ?)",
109+ (unicode(source_package_name), unicode(library_name), unicode(dependency), unicode(arch)))
110
111 def update_source_package(self, source_package_name, symbols):
112 """Update the database with the symbols from 'source_package_name'.
113@@ -372,9 +375,9 @@
114 self._store.execute("DELETE FROM libdep WHERE source_package_name = ?",
115 (unicode(source_package_name),))
116 for symbols in symbols:
117- for library, dependency in symbols:
118+ for library, dependency, architecture in symbols:
119 self.insert_new_library(
120- source_package_name, library, dependency)
121+ source_package_name, library, dependency, architecture)
122 self._store.commit()
123
124
125@@ -389,8 +392,8 @@
126 if not package:
127 return
128 symbols = [
129- list(iter_so_names(symbols))
130- for symbols in package.iter_symbols()]
131+ (list(iter_so_names(symbols)), architecture)
132+ for symbols, architecture in package.iter_symbols()]
133 db.update_source_package(package_name, symbols)
134
135
136
137=== modified file 'devportalbinary/tests/test_binary.py'
138--- devportalbinary/tests/test_binary.py 2012-07-24 15:05:27 +0000
139+++ devportalbinary/tests/test_binary.py 2012-07-24 17:18:20 +0000
140@@ -233,7 +233,7 @@
141
142 def test_guess_dependencies(self):
143 db = self.useFixture(DatabaseFixture()).db
144- db.update_source_package('eglibc', [[('libc.so.6', 'libc6')]])
145+ db.update_source_package('eglibc', [[('libc.so.6', 'libc6', 'i386')]])
146 deps = guess_dependencies(get_test_data_dir_path('hello'))
147 self.assertEqual(set(['libc6']), deps)
148
149@@ -302,7 +302,7 @@
150 shutil.copy(
151 get_test_data_file_path('hello', 'hello'), backend.path)
152 db = self.useFixture(DatabaseFixture()).db
153- db.update_source_package('eglibc', [[('libc.so.6', 'libc6')]])
154+ db.update_source_package('eglibc', [[('libc.so.6', 'libc6', 'i386')]])
155 expected_deps = ', '.join(guess_dependencies(backend.path))
156 build_deps = backend.get_build_depends(None)
157 self.assertEqual(expected_deps, build_deps)
158@@ -337,8 +337,8 @@
159 # The configuration file overrides the found library dependencies.
160 db = self.useFixture(DatabaseFixture()).db
161 db.update_source_package('foo', [
162- [('libasound.so.2', 'libfoo')],
163- [('libasound.so.2', 'libbar')],
164+ [('libasound.so.2', 'libfoo', 'i386')],
165+ [('libasound.so.2', 'libbar', 'i386')],
166 ])
167 self.assertEqual(
168 get_packages_for_libraries(["libasound.so.2"], "i386"),
169
170=== modified file 'devportalbinary/tests/test_database.py'
171--- devportalbinary/tests/test_database.py 2012-07-17 13:46:56 +0000
172+++ devportalbinary/tests/test_database.py 2012-07-24 17:18:20 +0000
173@@ -47,28 +47,28 @@
174
175 def test_insert_new_library(self):
176 db = self.get_package_db()
177- db.insert_new_library('foo-src', 'libfoo.so.0', 'foo')
178+ db.insert_new_library('foo-src', 'libfoo.so.0', 'foo', 'i386')
179 self.assertThat(
180- "SELECT source_package_name, library, dependency FROM libdep",
181- ResultsIn(db, [('foo-src', 'libfoo.so.0', 'foo')]))
182+ "SELECT source_package_name, library, dependency, architecture FROM libdep",
183+ ResultsIn(db, [('foo-src', 'libfoo.so.0', 'foo', 'i386')]))
184
185 def test_double_insert(self):
186 db = self.get_package_db()
187- db.insert_new_library('foo-src', 'libfoo.so.0', 'foo')
188+ db.insert_new_library('foo-src', 'libfoo.so.0', 'foo', 'i386')
189 self.assertRaises(
190 IntegrityError,
191- db.insert_new_library, 'foo-src', 'libfoo.so.0', 'foo')
192+ db.insert_new_library, 'foo-src', 'libfoo.so.0', 'foo', 'i386')
193
194 def test_differing_dependencies(self):
195 db = self.get_package_db()
196- db.insert_new_library('foo-src', 'libfoo.so.0', 'foo')
197- db.insert_new_library('foo-src', 'libfoo.so.0', 'bar')
198+ db.insert_new_library('foo-src', 'libfoo.so.0', 'foo', 'i386')
199+ db.insert_new_library('foo-src', 'libfoo.so.0', 'bar', 'i386')
200 deps = db.get_dependencies('libfoo.so.0')
201 self.assertEqual(deps, set(['foo', 'bar']))
202
203 def test_get_dependencies(self):
204 db = self.get_package_db()
205- db.insert_new_library('foo-src', 'libfoo.so.0', 'foo')
206+ db.insert_new_library('foo-src', 'libfoo.so.0', 'foo', 'i386')
207 deps = db.get_dependencies('libfoo.so.0')
208 self.assertEqual(deps, set(['foo']))
209
210@@ -80,13 +80,13 @@
211 def test_update_source_package(self):
212 db = self.get_package_db()
213 db.update_source_package(
214- 'foo', [[('libfoo.so.1', 'foo-bin')]])
215+ 'foo', [[('libfoo.so.1', 'foo-bin', 'i386')]])
216 deps = db.get_dependencies('libfoo.so.1')
217 self.assertEqual(deps, set(['foo-bin']))
218
219 def test_update_existing_source_package_no_libraries(self):
220 db = self.get_package_db()
221- db.update_source_package('foo', [[('libfoo.so.1', 'foo-bin')]])
222+ db.update_source_package('foo', [[('libfoo.so.1', 'foo-bin', 'i386')]])
223 # Run again, this time with no symbols, representing that a newer
224 # version of the package no longer exports any libraries.
225 db.update_source_package('foo', [])

Subscribers

People subscribed via source and target branches