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

Proposed by Chad Miller
Status: Merged
Approved by: Tim Cole
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
John O'Brien (community) Approve
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.
Revision history for this message
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 .

Revision history for this message
John O'Brien (jdobrien) wrote :

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

review: Approve
Revision history for this message
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

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
=== modified file 'bin/desktopcouch-pair'
--- bin/desktopcouch-pair 2009-08-26 18:01:29 +0000
+++ bin/desktopcouch-pair 2009-08-31 15:27:31 +0000
@@ -64,6 +64,7 @@
6464
65from desktopcouch.pair.couchdb_pairing import network_io65from desktopcouch.pair.couchdb_pairing import network_io
66from desktopcouch.pair.couchdb_pairing import dbus_io66from desktopcouch.pair.couchdb_pairing import dbus_io
67from desktopcouch.pair import pairing_record_type
6768
68discovery_tool_version = "1"69discovery_tool_version = "1"
6970
@@ -697,7 +698,7 @@
697 # Create a paired server record698 # Create a paired server record
698 service_data = CLOUD_SERVICES[name]699 service_data = CLOUD_SERVICES[name]
699 data = {700 data = {
700 "record_type": "http://www.freedesktop.org/wiki/Specifications/desktopcouch/paired_server",701 "record_type": pairing_record_type,
701 "pairing_identifier": str(uuid.uuid4()),702 "pairing_identifier": str(uuid.uuid4()),
702 "server": "%s:%s" % (hostname, port),703 "server": "%s:%s" % (hostname, port),
703 "oauth": {704 "oauth": {
@@ -733,6 +734,43 @@
733 success_note.run()734 success_note.run()
734 success_note.destroy()735 success_note.destroy()
735736
737
738def set_couchdb_bind_address():
739 from desktopcouch.records.server import CouchDatabase
740 from desktopcouch import local_files
741 bind_address = local_files.get_bind_address()
742
743 if bind_address not in ("127.0.0.1", "0.0.0.0", "::1", None):
744 logging.info("we're not qualified to change explicit address %s",
745 bind_address)
746 return False
747
748 db = CouchDatabase("management", create=True)
749 results = db.get_records(create_view=True)
750 count = 0
751 for row in results[pairing_record_type]:
752 if "server" in row.value and row.value["server"] != "":
753 # Is the record of something that probably connects back to us?
754 logging.debug("not counting fully-addressed machine %r", row.value["server"])
755 continue
756 count += 1
757 logging.debug("paired machine count is %d", count)
758 if count > 0:
759 if ":" in bind_address:
760 want_bind_address = "::0"
761 else:
762 want_bind_address = "0.0.0.0"
763 else:
764 if ":" in bind_address:
765 want_bind_address = "::0"
766 else:
767 want_bind_address = "127.0.0.1"
768
769 if bind_address != want_bind_address:
770 local_files.set_bind_address(want_bind_address)
771 logging.warning("changing the desktopcouch bind address from %r to %r",
772 bind_address, want_bind_address)
773
736def main(args):774def main(args):
737 """Start execution."""775 """Start execution."""
738 global pick_or_listen # pylint: disable-msg=W0601776 global pick_or_listen # pylint: disable-msg=W0601
@@ -747,6 +785,7 @@
747 pick_or_listen = PickOrListen()785 pick_or_listen = PickOrListen()
748 return run_program()786 return run_program()
749 finally:787 finally:
788 set_couchdb_bind_address()
750 logging.debug("exiting couchdb pairing tool")789 logging.debug("exiting couchdb pairing tool")
751790
752791
753792
=== modified file 'bin/desktopcouch-paired-replication-manager'
--- bin/desktopcouch-paired-replication-manager 2009-08-26 19:04:39 +0000
+++ bin/desktopcouch-paired-replication-manager 2009-08-30 12:25:52 +0000
@@ -32,6 +32,7 @@
32import xdg.BaseDirectory32import xdg.BaseDirectory
3333
34import desktopcouch34import desktopcouch
35from desktopcouch import local_files
35from desktopcouch.pair.couchdb_pairing import couchdb_io36from desktopcouch.pair.couchdb_pairing import couchdb_io
36from desktopcouch.pair.couchdb_pairing import dbus_io37from desktopcouch.pair.couchdb_pairing import dbus_io
3738
@@ -84,12 +85,37 @@
84 rotating_log.setLevel(logging.DEBUG)85 rotating_log.setLevel(logging.DEBUG)
85 formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')86 formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
86 rotating_log.setFormatter(formatter)87 rotating_log.setFormatter(formatter)
88 console = logging.StreamHandler()
89 console.setLevel(logging.WARNING)
87 logging.getLogger('').addHandler(rotating_log)90 logging.getLogger('').addHandler(rotating_log)
91 logging.getLogger('').addHandler(console)
88 logging.getLogger('').setLevel(logging.DEBUG)92 logging.getLogger('').setLevel(logging.DEBUG)
8993
90 try:94 try:
91 log.info("Starting.")95 log.info("Starting.")
9296
97 bind_addr = local_files.get_bind_address()
98 if bind_addr is not None and bind_addr != '':
99 from socket import inet_ntop, inet_pton
100 from socket import error as socketerror
101 try:
102 # There are more than dotted decimal quad to
103 # contend with. 183468213 == 0xaef80b5 ==
104 # 0xa.0xef.0x80.0xb5 == 012.0357.0200.0265 ==
105 # 10.239.128.181. Just ask the system instead.
106 addr_standard_form = inet_ntop(inet_pton(bind_addr))
107 except socketerror:
108 log.warn("bind address is illegal, %r", bind_addr)
109 log.warn("(If couchdb does understand it, please open a bug!)")
110 sys.exit(1)
111
112 if addr_standard_form.startswith("127."): # entire /8 is local.
113 log.warn("couchdb bound to addr %r; cannot accept connections",
114 bind_addr)
115 elif addr_standard_form == "::1": # no addr block on v6. Just one.
116 log.warn("couchdb bound to addr %r; cannot accept connections",
117 bind_addr)
118
93 unique_identifiers = couchdb_io.get_my_host_unique_id()119 unique_identifiers = couchdb_io.get_my_host_unique_id()
94 if unique_identifiers is None:120 if unique_identifiers is None:
95 log.warn("No unique hostaccount id is set, so pairing not enabled.")121 log.warn("No unique hostaccount id is set, so pairing not enabled.")
96122
=== modified file 'desktopcouch/local_files.py'
--- desktopcouch/local_files.py 2009-08-25 16:01:15 +0000
+++ desktopcouch/local_files.py 2009-08-31 15:27:31 +0000
@@ -26,7 +26,11 @@
26import os26import os
27import xdg.BaseDirectory27import xdg.BaseDirectory
28import subprocess28import subprocess
2929import logging
30try:
31 import ConfigParser as configparser
32except ImportError:
33 import configparser
3034
31def mkpath(rootdir, path):35def mkpath(rootdir, path):
32 "Remove .. from paths"36 "Remove .. from paths"
@@ -57,7 +61,10 @@
57 stdout=subprocess.PIPE)61 stdout=subprocess.PIPE)
58 line = process.stdout.read().split('\n')[0]62 line = process.stdout.read().split('\n')[0]
59 couchversion = line.split()[-1]63 couchversion = line.split()[-1]
60 if couchversion.startswith('0.1'):64
65 import distutils.version
66 if distutils.version.LooseVersion(couchversion) >= \
67 distutils.version.LooseVersion('0.10'):
61 chain = '-a'68 chain = '-a'
62 else:69 else:
63 chain = '-C'70 chain = '-C'
@@ -65,10 +72,13 @@
65 return chain72 return chain
6673
67class NoOAuthTokenException(Exception):74class NoOAuthTokenException(Exception):
75 def __init__(self, file_name):
76 super(Exception, self).__init__()
77 self.file_name = file_name
68 def __str__(self):78 def __str__(self):
69 return "OAuth details were not found in the ini file (%s)" % FILE_INI79 return "OAuth details were not found in the ini file (%s)" % self.file_name
7080
71def get_oauth_tokens():81def get_oauth_tokens(config_file_name=FILE_INI):
72 """Return the OAuth tokens from the desktop Couch ini file.82 """Return the OAuth tokens from the desktop Couch ini file.
73 CouchDB OAuth is two-legged OAuth (not three-legged like most OAuth).83 CouchDB OAuth is two-legged OAuth (not three-legged like most OAuth).
74 We have one "consumer", defined by a consumer_key and a secret,84 We have one "consumer", defined by a consumer_key and a secret,
@@ -78,18 +88,17 @@
78 (More traditional 3-legged OAuth starts with a "request token" which is88 (More traditional 3-legged OAuth starts with a "request token" which is
79 then used to procure an "access token". We do not require this.)89 then used to procure an "access token". We do not require this.)
80 """90 """
81 import ConfigParser91 c = configparser.ConfigParser()
82 c = ConfigParser.ConfigParser()
83 # monkeypatch ConfigParser to stop it lower-casing option names92 # monkeypatch ConfigParser to stop it lower-casing option names
84 c.optionxform = lambda s: s93 c.optionxform = lambda s: s
85 c.read(FILE_INI)94 c.read(config_file_name)
86 try:95 try:
87 oauth_token_secrets = c.items("oauth_token_secrets")96 oauth_token_secrets = c.items("oauth_token_secrets")
88 oauth_consumer_secrets = c.items("oauth_consumer_secrets")97 oauth_consumer_secrets = c.items("oauth_consumer_secrets")
89 except ConfigParser.NoSectionError:98 except configparser.NoSectionError:
90 raise NoOAuthTokenException99 raise NoOAuthTokenException(config_file_name)
91 if not oauth_token_secrets or not oauth_consumer_secrets:100 if not oauth_token_secrets or not oauth_consumer_secrets:
92 raise NoOAuthTokenException101 raise NoOAuthTokenException(config_file_name)
93 try:102 try:
94 out = {103 out = {
95 "token": oauth_token_secrets[0][0],104 "token": oauth_token_secrets[0][0],
@@ -98,9 +107,30 @@
98 "consumer_secret": oauth_consumer_secrets[0][1]107 "consumer_secret": oauth_consumer_secrets[0][1]
99 }108 }
100 except IndexError:109 except IndexError:
101 raise NoOAuthTokenException110 raise NoOAuthTokenException(config_file_name)
102 return out111 return out
103112
113
114def get_bind_address(config_file_name=FILE_INI):
115 """Retreive a string if it exists, or None if it doesn't."""
116 c = configparser.ConfigParser()
117 try:
118 c.read(config_file_name)
119 return c.get("httpd", "bind_address")
120 except (configparser.NoOptionError, OSError), e:
121 logging.warn("config file %r error. %s", config_file_name, e)
122 return None
123
124def set_bind_address(address, config_file_name=FILE_INI):
125 c = configparser.SafeConfigParser()
126 c.read(config_file_name)
127 if not c.has_section("httpd"):
128 c.add_section("httpd")
129 c.set("httpd", "bind_address", address)
130 with open(config_file_name, 'wb') as configfile:
131 c.write(configfile)
132
133
104# You will need to add -b or -k on the end of this134# You will need to add -b or -k on the end of this
105COUCH_EXEC_COMMAND = [COUCH_EXE, couch_chain_flag(), FILE_INI, '-p', FILE_PID,135COUCH_EXEC_COMMAND = [COUCH_EXE, couch_chain_flag(), FILE_INI, '-p', FILE_PID,
106 '-o', FILE_STDOUT, '-e', FILE_STDERR]136 '-o', FILE_STDOUT, '-e', FILE_STDERR]
107137
=== modified file 'desktopcouch/pair/__init__.py'
--- desktopcouch/pair/__init__.py 2009-07-08 17:48:11 +0000
+++ desktopcouch/pair/__init__.py 2009-08-31 15:27:31 +0000
@@ -14,3 +14,5 @@
14# You should have received a copy of the GNU Lesser General Public License14# You should have received a copy of the GNU Lesser General Public License
15# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.15# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
16"""The pair module."""16"""The pair module."""
17
18pairing_record_type = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/paired_server"
1719
=== modified file 'desktopcouch/records/server.py'
--- desktopcouch/records/server.py 2009-08-24 20:35:25 +0000
+++ desktopcouch/records/server.py 2009-08-31 15:28:46 +0000
@@ -44,6 +44,17 @@
44 "passing create=True)") % self.database44 "passing create=True)") % self.database
4545
4646
47def row_is_deleted(row):
48 """Test if a row is marked as deleted. Smart views 'maps' should not
49 return rows that are marked as deleted, so this function is not often
50 required."""
51 try:
52 return row['application_annotations']['Ubuntu One']\
53 ['private_application_annotations']['deleted']
54 except KeyError:
55 return False
56
57
47class CouchDatabase(object):58class CouchDatabase(object):
48 """An small records specific abstraction over a couch db database."""59 """An small records specific abstraction over a couch db database."""
4960
@@ -212,10 +223,12 @@
212 return []223 return []
213224
214 def get_records(self, record_type=None, create_view=False,225 def get_records(self, record_type=None, create_view=False,
215 design_doc=DEFAULT_DESIGN_DOCUMENT):226 design_doc=DEFAULT_DESIGN_DOCUMENT, version="1"):
216 """A convenience function to get records. We optionally create a view227 """A convenience function to get records from a view named
217 in the design document. C<create_view> may be True or False, and a228 C{get_records_and_type}, suffixed with C{__v} and the supplied version
218 special value, None, is analogous to O_EXCL|O_CREAT .229 string (or default of "1"). We optionally create a view in the design
230 document. C{create_view} may be True or False, and a special value,
231 None, is analogous to O_EXCL|O_CREAT .
219232
220 Set record_type to a string to retrieve records of only that233 Set record_type to a string to retrieve records of only that
221 specified type. Otherwise, usse the view to return *all* records.234 specified type. Otherwise, usse the view to return *all* records.
@@ -233,11 +246,14 @@
233 =>> people = results[['Person']:['Person','ZZZZ']]246 =>> people = results[['Person']:['Person','ZZZZ']]
234 """247 """
235 view_name = "get_records_and_type"248 view_name = "get_records_and_type"
236 view_map_js = """function(doc) { emit(doc.record_type, doc) }"""249 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); } }"""
237250
238 if design_doc is None:251 if design_doc is None:
239 design_doc = view_name252 design_doc = view_name
240253
254 if not version is None: # versions do not affect design_doc name.
255 view_name = view_name + "__v" + version
256
241 exists = self.view_exists(view_name, design_doc)257 exists = self.view_exists(view_name, design_doc)
242258
243 if exists:259 if exists:
@@ -255,4 +271,3 @@
255 return viewdata271 return viewdata
256 else:272 else:
257 return viewdata[record_type]273 return viewdata[record_type]
258
259274
=== modified file 'desktopcouch/records/tests/test_server.py'
--- desktopcouch/records/tests/test_server.py 2009-08-24 20:35:25 +0000
+++ desktopcouch/records/tests/test_server.py 2009-08-31 15:28:46 +0000
@@ -18,7 +18,7 @@
1818
19"""testing database/contact.py module"""19"""testing database/contact.py module"""
20import testtools20import testtools
21from desktopcouch.records.server import CouchDatabase21from desktopcouch.records.server import CouchDatabase, row_is_deleted
22from desktopcouch.records.record import Record22from desktopcouch.records.record import Record
23from desktopcouch.records.tests import get_uri23from desktopcouch.records.tests import get_uri
2424
@@ -146,10 +146,17 @@
146 other_record_type = "http://example.com/unittest/bad"146 other_record_type = "http://example.com/unittest/bad"
147147
148 for i in range(7):148 for i in range(7):
149 record = Record({'record_number': i},
150 record_type=good_record_type)
149 if i % 3 == 1:151 if i % 3 == 1:
150 record = Record({'record_number': i},152 record = Record({'record_number': i},
151 record_type=good_record_type)153 record_type=good_record_type)
152 record_ids_we_care_about.add(self.database.put_record(record))154 record_ids_we_care_about.add(self.database.put_record(record))
155 elif i % 3 == 2:
156 record = Record({'record_number': i},
157 record_type=good_record_type)
158 record_id = self.database.put_record(record) # correct type,
159 self.database.delete_record(record_id) # but marked deleted!
153 else:160 else:
154 record = Record({'record_number': i},161 record = Record({'record_number': i},
155 record_type=other_record_type)162 record_type=other_record_type)
@@ -160,6 +167,7 @@
160 for row in results[good_record_type]: # index notation167 for row in results[good_record_type]: # index notation
161 self.assertTrue(row.id in record_ids_we_care_about)168 self.assertTrue(row.id in record_ids_we_care_about)
162 record_ids_we_care_about.remove(row.id)169 record_ids_we_care_about.remove(row.id)
170 self.assertFalse(row_is_deleted(row))
163171
164 self.assertTrue(len(record_ids_we_care_about) == 0, "expected zero")172 self.assertTrue(len(record_ids_we_care_about) == 0, "expected zero")
165173

Subscribers

People subscribed via source and target branches