Merge lp:~cmiller/desktopcouch/pairing-fixups into lp:desktopcouch

Proposed by Chad Miller on 2009-08-31
Status: Merged
Approved by: Tim Cole on 2009-09-01
Approved revision: 53
Merged at revision: not available
Proposed branch: lp:~cmiller/desktopcouch/pairing-fixups
Merge into: lp:desktopcouch
Diff against target: None lines
To merge this branch: bzr merge lp:~cmiller/desktopcouch/pairing-fixups
Reviewer Review Type Date Requested Status
Tim Cole (community) Approve on 2009-09-01
John O'Brien (community) 2009-08-31 Approve on 2009-08-31
Review via email: mp+10926@code.launchpad.net

Commit message

In pairing and replication, be smarter about the bind address of the desktopcouch daemon.

Fix a problem with records marked as deleted not excluded from the default view code. Add versioning of view functions to force upgrades of old, bad, function.

To post a comment you must log in.
Chad Miller (cmiller) wrote :

Fix Bug#419969: at pairing time, change couchdb pairing address to public

Fix Bug#419973: in replication daemon, be sure local couchdb bind address is not 127/8 .

John O'Brien (jdobrien) wrote :

Looks good, tests run. I'm unsure how to test if this stuff works yet,

review: Approve
Tim Cole (tcole) wrote :

Looks reasonable. I don't really care for the JavaScript one-liner though; could it perhaps be formatted a little more legibly? (If you want the output JavaScript to be a one-liner, you still have the option of breaking the python string so that it at least appears formatted/indented reasonably in the Python source.)

review: Approve
54. By Chad Miller on 2009-09-01

Reformat JavaScript blob of code in server views code.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/desktopcouch-pair'
2--- bin/desktopcouch-pair 2009-08-26 18:01:29 +0000
3+++ bin/desktopcouch-pair 2009-08-31 15:27:31 +0000
4@@ -64,6 +64,7 @@
5
6 from desktopcouch.pair.couchdb_pairing import network_io
7 from desktopcouch.pair.couchdb_pairing import dbus_io
8+from desktopcouch.pair import pairing_record_type
9
10 discovery_tool_version = "1"
11
12@@ -697,7 +698,7 @@
13 # Create a paired server record
14 service_data = CLOUD_SERVICES[name]
15 data = {
16- "record_type": "http://www.freedesktop.org/wiki/Specifications/desktopcouch/paired_server",
17+ "record_type": pairing_record_type,
18 "pairing_identifier": str(uuid.uuid4()),
19 "server": "%s:%s" % (hostname, port),
20 "oauth": {
21@@ -733,6 +734,43 @@
22 success_note.run()
23 success_note.destroy()
24
25+
26+def set_couchdb_bind_address():
27+ from desktopcouch.records.server import CouchDatabase
28+ from desktopcouch import local_files
29+ bind_address = local_files.get_bind_address()
30+
31+ if bind_address not in ("127.0.0.1", "0.0.0.0", "::1", None):
32+ logging.info("we're not qualified to change explicit address %s",
33+ bind_address)
34+ return False
35+
36+ db = CouchDatabase("management", create=True)
37+ results = db.get_records(create_view=True)
38+ count = 0
39+ for row in results[pairing_record_type]:
40+ if "server" in row.value and row.value["server"] != "":
41+ # Is the record of something that probably connects back to us?
42+ logging.debug("not counting fully-addressed machine %r", row.value["server"])
43+ continue
44+ count += 1
45+ logging.debug("paired machine count is %d", count)
46+ if count > 0:
47+ if ":" in bind_address:
48+ want_bind_address = "::0"
49+ else:
50+ want_bind_address = "0.0.0.0"
51+ else:
52+ if ":" in bind_address:
53+ want_bind_address = "::0"
54+ else:
55+ want_bind_address = "127.0.0.1"
56+
57+ if bind_address != want_bind_address:
58+ local_files.set_bind_address(want_bind_address)
59+ logging.warning("changing the desktopcouch bind address from %r to %r",
60+ bind_address, want_bind_address)
61+
62 def main(args):
63 """Start execution."""
64 global pick_or_listen # pylint: disable-msg=W0601
65@@ -747,6 +785,7 @@
66 pick_or_listen = PickOrListen()
67 return run_program()
68 finally:
69+ set_couchdb_bind_address()
70 logging.debug("exiting couchdb pairing tool")
71
72
73
74=== modified file 'bin/desktopcouch-paired-replication-manager'
75--- bin/desktopcouch-paired-replication-manager 2009-08-26 19:04:39 +0000
76+++ bin/desktopcouch-paired-replication-manager 2009-08-30 12:25:52 +0000
77@@ -32,6 +32,7 @@
78 import xdg.BaseDirectory
79
80 import desktopcouch
81+from desktopcouch import local_files
82 from desktopcouch.pair.couchdb_pairing import couchdb_io
83 from desktopcouch.pair.couchdb_pairing import dbus_io
84
85@@ -84,12 +85,37 @@
86 rotating_log.setLevel(logging.DEBUG)
87 formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
88 rotating_log.setFormatter(formatter)
89+ console = logging.StreamHandler()
90+ console.setLevel(logging.WARNING)
91 logging.getLogger('').addHandler(rotating_log)
92+ logging.getLogger('').addHandler(console)
93 logging.getLogger('').setLevel(logging.DEBUG)
94
95 try:
96 log.info("Starting.")
97
98+ bind_addr = local_files.get_bind_address()
99+ if bind_addr is not None and bind_addr != '':
100+ from socket import inet_ntop, inet_pton
101+ from socket import error as socketerror
102+ try:
103+ # There are more than dotted decimal quad to
104+ # contend with. 183468213 == 0xaef80b5 ==
105+ # 0xa.0xef.0x80.0xb5 == 012.0357.0200.0265 ==
106+ # 10.239.128.181. Just ask the system instead.
107+ addr_standard_form = inet_ntop(inet_pton(bind_addr))
108+ except socketerror:
109+ log.warn("bind address is illegal, %r", bind_addr)
110+ log.warn("(If couchdb does understand it, please open a bug!)")
111+ sys.exit(1)
112+
113+ if addr_standard_form.startswith("127."): # entire /8 is local.
114+ log.warn("couchdb bound to addr %r; cannot accept connections",
115+ bind_addr)
116+ elif addr_standard_form == "::1": # no addr block on v6. Just one.
117+ log.warn("couchdb bound to addr %r; cannot accept connections",
118+ bind_addr)
119+
120 unique_identifiers = couchdb_io.get_my_host_unique_id()
121 if unique_identifiers is None:
122 log.warn("No unique hostaccount id is set, so pairing not enabled.")
123
124=== modified file 'desktopcouch/local_files.py'
125--- desktopcouch/local_files.py 2009-08-25 16:01:15 +0000
126+++ desktopcouch/local_files.py 2009-08-31 15:27:31 +0000
127@@ -26,7 +26,11 @@
128 import os
129 import xdg.BaseDirectory
130 import subprocess
131-
132+import logging
133+try:
134+ import ConfigParser as configparser
135+except ImportError:
136+ import configparser
137
138 def mkpath(rootdir, path):
139 "Remove .. from paths"
140@@ -57,7 +61,10 @@
141 stdout=subprocess.PIPE)
142 line = process.stdout.read().split('\n')[0]
143 couchversion = line.split()[-1]
144- if couchversion.startswith('0.1'):
145+
146+ import distutils.version
147+ if distutils.version.LooseVersion(couchversion) >= \
148+ distutils.version.LooseVersion('0.10'):
149 chain = '-a'
150 else:
151 chain = '-C'
152@@ -65,10 +72,13 @@
153 return chain
154
155 class NoOAuthTokenException(Exception):
156+ def __init__(self, file_name):
157+ super(Exception, self).__init__()
158+ self.file_name = file_name
159 def __str__(self):
160- return "OAuth details were not found in the ini file (%s)" % FILE_INI
161+ return "OAuth details were not found in the ini file (%s)" % self.file_name
162
163-def get_oauth_tokens():
164+def get_oauth_tokens(config_file_name=FILE_INI):
165 """Return the OAuth tokens from the desktop Couch ini file.
166 CouchDB OAuth is two-legged OAuth (not three-legged like most OAuth).
167 We have one "consumer", defined by a consumer_key and a secret,
168@@ -78,18 +88,17 @@
169 (More traditional 3-legged OAuth starts with a "request token" which is
170 then used to procure an "access token". We do not require this.)
171 """
172- import ConfigParser
173- c = ConfigParser.ConfigParser()
174+ c = configparser.ConfigParser()
175 # monkeypatch ConfigParser to stop it lower-casing option names
176 c.optionxform = lambda s: s
177- c.read(FILE_INI)
178+ c.read(config_file_name)
179 try:
180 oauth_token_secrets = c.items("oauth_token_secrets")
181 oauth_consumer_secrets = c.items("oauth_consumer_secrets")
182- except ConfigParser.NoSectionError:
183- raise NoOAuthTokenException
184+ except configparser.NoSectionError:
185+ raise NoOAuthTokenException(config_file_name)
186 if not oauth_token_secrets or not oauth_consumer_secrets:
187- raise NoOAuthTokenException
188+ raise NoOAuthTokenException(config_file_name)
189 try:
190 out = {
191 "token": oauth_token_secrets[0][0],
192@@ -98,9 +107,30 @@
193 "consumer_secret": oauth_consumer_secrets[0][1]
194 }
195 except IndexError:
196- raise NoOAuthTokenException
197+ raise NoOAuthTokenException(config_file_name)
198 return out
199
200+
201+def get_bind_address(config_file_name=FILE_INI):
202+ """Retreive a string if it exists, or None if it doesn't."""
203+ c = configparser.ConfigParser()
204+ try:
205+ c.read(config_file_name)
206+ return c.get("httpd", "bind_address")
207+ except (configparser.NoOptionError, OSError), e:
208+ logging.warn("config file %r error. %s", config_file_name, e)
209+ return None
210+
211+def set_bind_address(address, config_file_name=FILE_INI):
212+ c = configparser.SafeConfigParser()
213+ c.read(config_file_name)
214+ if not c.has_section("httpd"):
215+ c.add_section("httpd")
216+ c.set("httpd", "bind_address", address)
217+ with open(config_file_name, 'wb') as configfile:
218+ c.write(configfile)
219+
220+
221 # You will need to add -b or -k on the end of this
222 COUCH_EXEC_COMMAND = [COUCH_EXE, couch_chain_flag(), FILE_INI, '-p', FILE_PID,
223 '-o', FILE_STDOUT, '-e', FILE_STDERR]
224
225=== modified file 'desktopcouch/pair/__init__.py'
226--- desktopcouch/pair/__init__.py 2009-07-08 17:48:11 +0000
227+++ desktopcouch/pair/__init__.py 2009-08-31 15:27:31 +0000
228@@ -14,3 +14,5 @@
229 # You should have received a copy of the GNU Lesser General Public License
230 # along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
231 """The pair module."""
232+
233+pairing_record_type = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/paired_server"
234
235=== modified file 'desktopcouch/records/server.py'
236--- desktopcouch/records/server.py 2009-08-24 20:35:25 +0000
237+++ desktopcouch/records/server.py 2009-08-31 15:28:46 +0000
238@@ -44,6 +44,17 @@
239 "passing create=True)") % self.database
240
241
242+def row_is_deleted(row):
243+ """Test if a row is marked as deleted. Smart views 'maps' should not
244+ return rows that are marked as deleted, so this function is not often
245+ required."""
246+ try:
247+ return row['application_annotations']['Ubuntu One']\
248+ ['private_application_annotations']['deleted']
249+ except KeyError:
250+ return False
251+
252+
253 class CouchDatabase(object):
254 """An small records specific abstraction over a couch db database."""
255
256@@ -212,10 +223,12 @@
257 return []
258
259 def get_records(self, record_type=None, create_view=False,
260- design_doc=DEFAULT_DESIGN_DOCUMENT):
261- """A convenience function to get records. We optionally create a view
262- in the design document. C<create_view> may be True or False, and a
263- special value, None, is analogous to O_EXCL|O_CREAT .
264+ design_doc=DEFAULT_DESIGN_DOCUMENT, version="1"):
265+ """A convenience function to get records from a view named
266+ C{get_records_and_type}, suffixed with C{__v} and the supplied version
267+ string (or default of "1"). We optionally create a view in the design
268+ document. C{create_view} may be True or False, and a special value,
269+ None, is analogous to O_EXCL|O_CREAT .
270
271 Set record_type to a string to retrieve records of only that
272 specified type. Otherwise, usse the view to return *all* records.
273@@ -233,11 +246,14 @@
274 =>> people = results[['Person']:['Person','ZZZZ']]
275 """
276 view_name = "get_records_and_type"
277- view_map_js = """function(doc) { emit(doc.record_type, doc) }"""
278+ view_map_js = """function(doc) { try {if (! doc['application_annotations']['Ubuntu One']['private_application_annotations']['deleted']) { emit(doc.record_type, doc);} } catch (e) { emit(doc.record_type, doc); } }"""
279
280 if design_doc is None:
281 design_doc = view_name
282
283+ if not version is None: # versions do not affect design_doc name.
284+ view_name = view_name + "__v" + version
285+
286 exists = self.view_exists(view_name, design_doc)
287
288 if exists:
289@@ -255,4 +271,3 @@
290 return viewdata
291 else:
292 return viewdata[record_type]
293-
294
295=== modified file 'desktopcouch/records/tests/test_server.py'
296--- desktopcouch/records/tests/test_server.py 2009-08-24 20:35:25 +0000
297+++ desktopcouch/records/tests/test_server.py 2009-08-31 15:28:46 +0000
298@@ -18,7 +18,7 @@
299
300 """testing database/contact.py module"""
301 import testtools
302-from desktopcouch.records.server import CouchDatabase
303+from desktopcouch.records.server import CouchDatabase, row_is_deleted
304 from desktopcouch.records.record import Record
305 from desktopcouch.records.tests import get_uri
306
307@@ -146,10 +146,17 @@
308 other_record_type = "http://example.com/unittest/bad"
309
310 for i in range(7):
311+ record = Record({'record_number': i},
312+ record_type=good_record_type)
313 if i % 3 == 1:
314 record = Record({'record_number': i},
315 record_type=good_record_type)
316 record_ids_we_care_about.add(self.database.put_record(record))
317+ elif i % 3 == 2:
318+ record = Record({'record_number': i},
319+ record_type=good_record_type)
320+ record_id = self.database.put_record(record) # correct type,
321+ self.database.delete_record(record_id) # but marked deleted!
322 else:
323 record = Record({'record_number': i},
324 record_type=other_record_type)
325@@ -160,6 +167,7 @@
326 for row in results[good_record_type]: # index notation
327 self.assertTrue(row.id in record_ids_we_care_about)
328 record_ids_we_care_about.remove(row.id)
329+ self.assertFalse(row_is_deleted(row))
330
331 self.assertTrue(len(record_ids_we_care_about) == 0, "expected zero")
332

Subscribers

People subscribed via source and target branches