Merge lp://qastaging/~cmiller/desktopcouch/getport-at-call-time into lp://qastaging/desktopcouch
- getport-at-call-time
- Merge into trunk
Proposed by
Chad Miller
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp://qastaging/~cmiller/desktopcouch/getport-at-call-time | ||||
Merge into: | lp://qastaging/desktopcouch | ||||
Diff against target: | None lines | ||||
To merge this branch: | bzr merge lp://qastaging/~cmiller/desktopcouch/getport-at-call-time | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu One hackers | Pending | ||
Review via email:
|
This proposal has been superseded by a proposal from 2009-08-13.
Commit message
Description of the change
To post a comment you must log in.
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'desktopcouch/__init__.py' | |||
2 | --- desktopcouch/__init__.py 2009-08-04 11:15:52 +0000 | |||
3 | +++ desktopcouch/__init__.py 2009-08-13 12:12:22 +0000 | |||
4 | @@ -21,52 +21,66 @@ | |||
5 | 21 | import errno | 21 | import errno |
6 | 22 | import time | 22 | import time |
7 | 23 | 23 | ||
9 | 24 | def find_pid(): | 24 | def find_pid(start_if_not_running=True): |
10 | 25 | # Work out whether CouchDB is running by looking at its pid file | 25 | # Work out whether CouchDB is running by looking at its pid file |
19 | 26 | from desktopcouch import local_files | 26 | def get_pid(): |
20 | 27 | pid = '' | 27 | from desktopcouch import local_files |
21 | 28 | try: | 28 | try: |
22 | 29 | fp = open(local_files.FILE_PID) | 29 | pid_file = local_files.FILE_PID |
23 | 30 | pid = int(fp.read()) | 30 | with open(pid_file) as fp: |
24 | 31 | fp.close() | 31 | try: |
25 | 32 | except IOError: | 32 | return int(fp.read()) |
26 | 33 | pass | 33 | except ValueError: |
27 | 34 | return None | ||
28 | 35 | except IOError: | ||
29 | 36 | return None | ||
30 | 34 | 37 | ||
32 | 35 | if not is_couchdb(pid): | 38 | pid = get_pid() |
33 | 39 | if not process_is_couchdb(pid) and start_if_not_running: | ||
34 | 36 | # pidfile is stale | 40 | # pidfile is stale |
35 | 37 | # start CouchDB by running the startup script | 41 | # start CouchDB by running the startup script |
37 | 38 | print "Desktop CouchDB is not running; starting it." | 42 | print "Desktop CouchDB is not running; starting it.", |
38 | 39 | from desktopcouch import start_local_couchdb | 43 | from desktopcouch import start_local_couchdb |
39 | 40 | start_local_couchdb.start_couchdb() | 44 | start_local_couchdb.start_couchdb() |
41 | 41 | time.sleep(2) # give the process a chance to start | 45 | for timeout in xrange(1000): |
42 | 46 | pid = get_pid() | ||
43 | 47 | if process_is_couchdb(pid): | ||
44 | 48 | break | ||
45 | 49 | print ".", | ||
46 | 50 | time.sleep(0.1) | ||
47 | 42 | 51 | ||
54 | 43 | # get the pid | 52 | if process_is_couchdb(pid): |
55 | 44 | try: | 53 | print " done." |
56 | 45 | with open(local_files.FILE_PID) as pid_file: | 54 | else: |
57 | 46 | pid = int(pid_file.read().strip()) | 55 | print " failed." |
52 | 47 | except IOError, e: | ||
53 | 48 | if e.errno == ENOENT: | ||
58 | 49 | raise RuntimeError("desktop-couch not started") | 56 | raise RuntimeError("desktop-couch not started") |
61 | 50 | else: | 57 | |
60 | 51 | raise | ||
62 | 52 | return pid | 58 | return pid |
63 | 53 | 59 | ||
65 | 54 | def is_couchdb(pid): | 60 | def process_is_couchdb__linux(pid): |
66 | 61 | if not isinstance(pid, int): | ||
67 | 62 | return False | ||
68 | 63 | |||
69 | 55 | proc_dir = "/proc/%s" % (pid,) | 64 | proc_dir = "/proc/%s" % (pid,) |
70 | 56 | 65 | ||
79 | 57 | # check to make sure that the process still exists | 66 | try: |
80 | 58 | if not os.path.isdir(proc_dir): | 67 | # check to make sure it is actually a desktop-couch instance |
81 | 59 | return False | 68 | with open(os.path.join(proc_dir, 'cmdline')) as cmd_file: |
82 | 60 | 69 | cmd = cmd_file.read() | |
83 | 61 | # check to make sure it is actually a desktop-couch instance | 70 | if '/desktop-couch' not in cmd: |
84 | 62 | with open(os.path.join(proc_dir, 'cmdline')) as cmd_file: | 71 | return False |
85 | 63 | cmd = cmd_file.read() | 72 | |
86 | 64 | if re.search('desktop-couch', cmd) is None: | 73 | # make sure it's our process. |
87 | 74 | if not os.access(os.path.join(proc_dir, "mem"), os.W_OK): | ||
88 | 75 | return False | ||
89 | 76 | |||
90 | 77 | except IOError: | ||
91 | 65 | return False | 78 | return False |
92 | 66 | 79 | ||
93 | 67 | return True | 80 | return True |
94 | 68 | 81 | ||
96 | 69 | def find_port(pid): | 82 | |
97 | 83 | def find_port__linux(pid): | ||
98 | 70 | # Look in the CouchDB log to find the port number, someday. | 84 | # Look in the CouchDB log to find the port number, someday. |
99 | 71 | # Currently, we have to grovel around in /proc instead. | 85 | # Currently, we have to grovel around in /proc instead. |
100 | 72 | # Oh, the huge manatee... (this replaced an lsof shell recipe | 86 | # Oh, the huge manatee... (this replaced an lsof shell recipe |
101 | @@ -76,8 +90,12 @@ | |||
102 | 76 | 90 | ||
103 | 77 | # enumerate the process' file descriptors | 91 | # enumerate the process' file descriptors |
104 | 78 | fd_dir = os.path.join(proc_dir, 'fd') | 92 | fd_dir = os.path.join(proc_dir, 'fd') |
107 | 79 | fd_paths = [os.readlink(os.path.join(fd_dir, fd)) | 93 | try: |
108 | 80 | for fd in os.listdir(fd_dir)] | 94 | fd_paths = [os.readlink(os.path.join(fd_dir, fd)) |
109 | 95 | for fd in os.listdir(fd_dir)] | ||
110 | 96 | except OSError: | ||
111 | 97 | raise RuntimeError("Unable to find file descriptors in /proc") | ||
112 | 98 | |||
113 | 81 | 99 | ||
114 | 82 | # identify socket fds | 100 | # identify socket fds |
115 | 83 | socket_matches = [re.match('socket:\\[([0-9]+)\\]', p) for p in fd_paths] | 101 | socket_matches = [re.match('socket:\\[([0-9]+)\\]', p) for p in fd_paths] |
116 | @@ -108,9 +126,24 @@ | |||
117 | 108 | for line in tcp_file: | 126 | for line in tcp_file: |
118 | 109 | match = listening_regexp.match(line) | 127 | match = listening_regexp.match(line) |
119 | 110 | if match is not None: | 128 | if match is not None: |
121 | 111 | port = int(match.group(1), 16) | 129 | port = str(int(match.group(1), 16)) |
122 | 112 | break | 130 | break |
123 | 113 | if port is None: | 131 | if port is None: |
124 | 114 | raise RuntimeError("Unable to find listening port") | 132 | raise RuntimeError("Unable to find listening port") |
125 | 115 | 133 | ||
127 | 116 | return str(port) | 134 | return port |
128 | 135 | |||
129 | 136 | |||
130 | 137 | |||
131 | 138 | import platform | ||
132 | 139 | os_name = platform.system() | ||
133 | 140 | try: | ||
134 | 141 | process_is_couchdb = { | ||
135 | 142 | "Linux":process_is_couchdb__linux | ||
136 | 143 | } [os_name] | ||
137 | 144 | |||
138 | 145 | find_port = { | ||
139 | 146 | "Linux":find_port__linux | ||
140 | 147 | } [os_name] | ||
141 | 148 | except KeyError: | ||
142 | 149 | raise NotImplementedError("os %r is not yet supported" % (os_name,)) | ||
143 | 117 | 150 | ||
144 | === modified file 'desktopcouch/records/tests/test_field_registry.py' | |||
145 | --- desktopcouch/records/tests/test_field_registry.py 2009-07-08 17:48:11 +0000 | |||
146 | +++ desktopcouch/records/tests/test_field_registry.py 2009-08-13 16:06:20 +0000 | |||
147 | @@ -18,7 +18,7 @@ | |||
148 | 18 | """Test cases for field mapping""" | 18 | """Test cases for field mapping""" |
149 | 19 | 19 | ||
150 | 20 | import copy | 20 | import copy |
152 | 21 | from twisted.trial.unittest import TestCase as TwistedTestCase | 21 | from testtools import TestCase |
153 | 22 | from desktopcouch.records.field_registry import ( | 22 | from desktopcouch.records.field_registry import ( |
154 | 23 | SimpleFieldMapping, MergeableListFieldMapping, Transformer) | 23 | SimpleFieldMapping, MergeableListFieldMapping, Transformer) |
155 | 24 | from desktopcouch.records.record import Record | 24 | from desktopcouch.records.record import Record |
156 | @@ -53,7 +53,7 @@ | |||
157 | 53 | super(AppTransformer, self).__init__('Test App', field_registry) | 53 | super(AppTransformer, self).__init__('Test App', field_registry) |
158 | 54 | 54 | ||
159 | 55 | 55 | ||
161 | 56 | class TestFieldMapping(TwistedTestCase): | 56 | class TestFieldMapping(TestCase): |
162 | 57 | """Test Case for FieldMapping objects.""" | 57 | """Test Case for FieldMapping objects.""" |
163 | 58 | 58 | ||
164 | 59 | def setUp(self): | 59 | def setUp(self): |
165 | @@ -85,7 +85,7 @@ | |||
166 | 85 | self.assertEqual(None, mapping.getValue(record)) | 85 | self.assertEqual(None, mapping.getValue(record)) |
167 | 86 | 86 | ||
168 | 87 | 87 | ||
170 | 88 | class TestTransformer(TwistedTestCase): | 88 | class TestTransformer(TestCase): |
171 | 89 | """Test application specific transformer classes""" | 89 | """Test application specific transformer classes""" |
172 | 90 | 90 | ||
173 | 91 | def setUp(self): | 91 | def setUp(self): |
174 | 92 | 92 | ||
175 | === modified file 'desktopcouch/records/tests/test_record.py' | |||
176 | --- desktopcouch/records/tests/test_record.py 2009-07-08 17:48:11 +0000 | |||
177 | +++ desktopcouch/records/tests/test_record.py 2009-08-13 16:06:20 +0000 | |||
178 | @@ -18,7 +18,7 @@ | |||
179 | 18 | 18 | ||
180 | 19 | """Tests for the RecordDict object on which the Contacts API is built.""" | 19 | """Tests for the RecordDict object on which the Contacts API is built.""" |
181 | 20 | 20 | ||
183 | 21 | from twisted.trial.unittest import TestCase as TwistedTestCase | 21 | from testtools import TestCase |
184 | 22 | 22 | ||
185 | 23 | # pylint does not like relative imports from containing packages | 23 | # pylint does not like relative imports from containing packages |
186 | 24 | # pylint: disable-msg=F0401 | 24 | # pylint: disable-msg=F0401 |
187 | @@ -26,7 +26,7 @@ | |||
188 | 26 | record_factory, IllegalKeyException, validate) | 26 | record_factory, IllegalKeyException, validate) |
189 | 27 | 27 | ||
190 | 28 | 28 | ||
192 | 29 | class TestRecords(TwistedTestCase): | 29 | class TestRecords(TestCase): |
193 | 30 | """Test the record functionality""" | 30 | """Test the record functionality""" |
194 | 31 | 31 | ||
195 | 32 | def setUp(self): | 32 | def setUp(self): |
196 | @@ -180,7 +180,7 @@ | |||
197 | 180 | self.record.record_type) | 180 | self.record.record_type) |
198 | 181 | 181 | ||
199 | 182 | 182 | ||
201 | 183 | class TestRecordFactory(TwistedTestCase): | 183 | class TestRecordFactory(TestCase): |
202 | 184 | """Test Record/Mergeable List factories.""" | 184 | """Test Record/Mergeable List factories.""" |
203 | 185 | 185 | ||
204 | 186 | def setUp(self): | 186 | def setUp(self): |
205 | 187 | 187 | ||
206 | === modified file 'desktopcouch/records/tests/test_server.py' | |||
207 | --- desktopcouch/records/tests/test_server.py 2009-08-10 21:32:52 +0000 | |||
208 | +++ desktopcouch/records/tests/test_server.py 2009-08-12 14:26:40 +0000 | |||
209 | @@ -19,7 +19,8 @@ | |||
210 | 19 | """testing database/contact.py module""" | 19 | """testing database/contact.py module""" |
211 | 20 | 20 | ||
212 | 21 | import testtools | 21 | import testtools |
214 | 22 | 22 | import random | |
215 | 23 | from desktopcouch.stop_local_couchdb import stop_couchdb | ||
216 | 23 | from desktopcouch.records.server import CouchDatabase | 24 | from desktopcouch.records.server import CouchDatabase |
217 | 24 | from desktopcouch.records.record import Record | 25 | from desktopcouch.records.record import Record |
218 | 25 | 26 | ||
219 | @@ -49,6 +50,9 @@ | |||
220 | 49 | def tearDown(self): | 50 | def tearDown(self): |
221 | 50 | """tear down each test""" | 51 | """tear down each test""" |
222 | 51 | del self.database._server[self.dbname] | 52 | del self.database._server[self.dbname] |
223 | 53 | if random.choice([1,2,3,4]) == 3: # don't harass it unnecessarily | ||
224 | 54 | print u"\u2620", # death | ||
225 | 55 | stop_couchdb() | ||
226 | 52 | 56 | ||
227 | 53 | def test_get_records_by_record_type_save_view(self): | 57 | def test_get_records_by_record_type_save_view(self): |
228 | 54 | """Test getting mutliple records by type""" | 58 | """Test getting mutliple records by type""" |
229 | 55 | 59 | ||
230 | === modified file 'desktopcouch/start_local_couchdb.py' (properties changed: -x to +x) | |||
231 | --- desktopcouch/start_local_couchdb.py 2009-07-27 18:16:19 +0000 | |||
232 | +++ desktopcouch/start_local_couchdb.py 2009-08-13 12:14:05 +0000 | |||
233 | @@ -32,10 +32,12 @@ | |||
234 | 32 | """ | 32 | """ |
235 | 33 | 33 | ||
236 | 34 | from __future__ import with_statement | 34 | from __future__ import with_statement |
238 | 35 | import os, subprocess, sys | 35 | import os, sys |
239 | 36 | import subprocess | ||
240 | 36 | import desktopcouch | 37 | import desktopcouch |
241 | 37 | from desktopcouch import local_files | 38 | from desktopcouch import local_files |
242 | 38 | import xdg.BaseDirectory | 39 | import xdg.BaseDirectory |
243 | 40 | import errno | ||
244 | 39 | import time | 41 | import time |
245 | 40 | 42 | ||
246 | 41 | def dump_ini(data, filename): | 43 | def dump_ini(data, filename): |
247 | @@ -52,8 +54,6 @@ | |||
248 | 52 | fd.write("\n") | 54 | fd.write("\n") |
249 | 53 | fd.close() | 55 | fd.close() |
250 | 54 | 56 | ||
251 | 55 | |||
252 | 56 | |||
253 | 57 | def create_ini_file(): | 57 | def create_ini_file(): |
254 | 58 | """Write CouchDB ini file if not already present""" | 58 | """Write CouchDB ini file if not already present""" |
255 | 59 | # FIXME add update trigger folder | 59 | # FIXME add update trigger folder |
256 | @@ -89,7 +89,16 @@ | |||
257 | 89 | """Actually start the CouchDB process""" | 89 | """Actually start the CouchDB process""" |
258 | 90 | local_exec = local_files.COUCH_EXEC_COMMAND + ['-b'] | 90 | local_exec = local_files.COUCH_EXEC_COMMAND + ['-b'] |
259 | 91 | try: | 91 | try: |
261 | 92 | retcode = subprocess.call(local_exec, shell=False) | 92 | # subprocess is buggy. Chad patched, but that takes time to propagate. |
262 | 93 | proc = subprocess.Popen(local_exec) | ||
263 | 94 | while True: | ||
264 | 95 | try: | ||
265 | 96 | retcode = proc.wait() | ||
266 | 97 | break | ||
267 | 98 | except OSError, e: | ||
268 | 99 | if e.errno == errno.EINTR: | ||
269 | 100 | continue | ||
270 | 101 | raise | ||
271 | 93 | if retcode < 0: | 102 | if retcode < 0: |
272 | 94 | print >> sys.stderr, "Child was terminated by signal", -retcode | 103 | print >> sys.stderr, "Child was terminated by signal", -retcode |
273 | 95 | elif retcode > 0: | 104 | elif retcode > 0: |
274 | @@ -118,15 +127,30 @@ | |||
275 | 118 | html = fp.read() | 127 | html = fp.read() |
276 | 119 | fp.close() | 128 | fp.close() |
277 | 120 | 129 | ||
281 | 121 | time.sleep(1) | 130 | port = None |
282 | 122 | pid = desktopcouch.find_pid() | 131 | for retry in xrange(10000, 0, -1): |
283 | 123 | port = desktopcouch.find_port(pid) | 132 | pid = desktopcouch.find_pid(start_if_not_running=False) |
284 | 133 | try: | ||
285 | 134 | port = desktopcouch.find_port(pid) | ||
286 | 135 | break | ||
287 | 136 | except RuntimeError, e: | ||
288 | 137 | if retry == 1: | ||
289 | 138 | raise | ||
290 | 139 | time.sleep(0.01) | ||
291 | 140 | continue | ||
292 | 124 | 141 | ||
298 | 125 | fp = open(bookmark_file, "w") | 142 | if port is None: |
299 | 126 | fp.write(html.replace("[[COUCHDB_PORT]]", port)) | 143 | print "We couldn't find desktop-CouchDB's network port. Bookmark file not written." |
300 | 127 | fp.close() | 144 | try: |
301 | 128 | print "Browse your desktop CouchDB at file://%s" % \ | 145 | os.remove(bookmark_file) |
302 | 129 | os.path.realpath(bookmark_file) | 146 | except OSError: |
303 | 147 | pass | ||
304 | 148 | else: | ||
305 | 149 | fp = open(bookmark_file, "w") | ||
306 | 150 | fp.write(html.replace("[[COUCHDB_PORT]]", port)) | ||
307 | 151 | fp.close() | ||
308 | 152 | print "Browse your desktop CouchDB at file://%s" % \ | ||
309 | 153 | os.path.realpath(bookmark_file) | ||
310 | 130 | 154 | ||
311 | 131 | def start_couchdb(): | 155 | def start_couchdb(): |
312 | 132 | """Execute each step to start a desktop CouchDB""" | 156 | """Execute each step to start a desktop CouchDB""" |
313 | @@ -135,6 +159,7 @@ | |||
314 | 135 | update_design_documents() | 159 | update_design_documents() |
315 | 136 | write_bookmark_file() | 160 | write_bookmark_file() |
316 | 137 | 161 | ||
317 | 162 | |||
318 | 138 | if __name__ == "__main__": | 163 | if __name__ == "__main__": |
319 | 139 | start_couchdb() | 164 | start_couchdb() |
320 | 140 | print "Desktop CouchDB started" | 165 | print "Desktop CouchDB started" |
321 | 141 | 166 | ||
322 | === added file 'desktopcouch/stop_local_couchdb.py' | |||
323 | --- desktopcouch/stop_local_couchdb.py 1970-01-01 00:00:00 +0000 | |||
324 | +++ desktopcouch/stop_local_couchdb.py 2009-08-12 14:26:14 +0000 | |||
325 | @@ -0,0 +1,49 @@ | |||
326 | 1 | #!/usr/bin/python | ||
327 | 2 | # Copyright 2009 Canonical Ltd. | ||
328 | 3 | # | ||
329 | 4 | # This file is part of desktopcouch. | ||
330 | 5 | # | ||
331 | 6 | # desktopcouch is free software: you can redistribute it and/or modify | ||
332 | 7 | # it under the terms of the GNU Lesser General Public License version 3 | ||
333 | 8 | # as published by the Free Software Foundation. | ||
334 | 9 | # | ||
335 | 10 | # desktopcouch is distributed in the hope that it will be useful, | ||
336 | 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
337 | 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
338 | 13 | # GNU Lesser General Public License for more details. | ||
339 | 14 | # | ||
340 | 15 | # You should have received a copy of the GNU Lesser General Public License | ||
341 | 16 | # along with desktopcouch. If not, see <http://www.gnu.org/licenses/>. | ||
342 | 17 | # | ||
343 | 18 | # Author: Chad Miller <chad.miller@canonical.com> | ||
344 | 19 | """ | ||
345 | 20 | Stop local CouchDB server. | ||
346 | 21 | """ | ||
347 | 22 | |||
348 | 23 | import os | ||
349 | 24 | import desktopcouch | ||
350 | 25 | import time | ||
351 | 26 | import signal | ||
352 | 27 | import errno | ||
353 | 28 | |||
354 | 29 | def stop_couchdb(): | ||
355 | 30 | pid = desktopcouch.find_pid(start_if_not_running=False) | ||
356 | 31 | while pid is not None: | ||
357 | 32 | try: | ||
358 | 33 | os.kill(pid, signal.SIGTERM) | ||
359 | 34 | except OSError, e: | ||
360 | 35 | if e.errno == errno.ESRCH: | ||
361 | 36 | break | ||
362 | 37 | raise | ||
363 | 38 | |||
364 | 39 | for retry in xrange(300): | ||
365 | 40 | try: | ||
366 | 41 | os.kill(pid, 0) # test existence. sig-zero is special. | ||
367 | 42 | except OSError: | ||
368 | 43 | break | ||
369 | 44 | time.sleep(0.01) | ||
370 | 45 | |||
371 | 46 | pid = desktopcouch.find_pid(start_if_not_running=False) | ||
372 | 47 | |||
373 | 48 | if __name__ == "__main__": | ||
374 | 49 | stop_couchdb() |