Merge lp:~cmiller/desktopcouch/getport-at-call-time into lp:desktopcouch

Proposed by Chad Miller
Status: Merged
Approved by: Elliot Murphy
Approved revision: 44
Merged at revision: not available
Proposed branch: lp:~cmiller/desktopcouch/getport-at-call-time
Merge into: lp:desktopcouch
Diff against target: None lines
To merge this branch: bzr merge lp:~cmiller/desktopcouch/getport-at-call-time
Reviewer Review Type Date Requested Status
Elliot Murphy (community) Approve
Eric Casteleijn (community) Approve
Review via email: mp+10105@code.launchpad.net

This proposal supersedes a proposal from 2009-08-13.

Commit message

Use subprocess.Popen and ourselves to the wait()ing, since subprocess.call() is buggy. There's still an EINTR bug in subprocess, though.

Occasionally stop couchdb in tests, so we exercise the automatic starting code. This will lead to spurious errors because of the aforementioned subprocess bug, but it's the right thing to do.

Abstract away some of the linuxisms and complain if we're run on an unsupported OS.

Fix a race condition in the process-testing code.

Replace the TestCase module with one that doesn't complain of dirty twisted reactors.

Add a means of stopping the desktop couchdb daemon.

Add an additional check that a found PID and process named correctly is indeed a process that this user started, so we don't try to talk to other local users' desktop couchdbs.

To post a comment you must log in.
Revision history for this message
Eric Casteleijn (thisfred) wrote :

Tests run, code changes look good to me.

review: Approve
Revision history for this message
Elliot Murphy (statik) wrote :

yay, desperately needed cleanup.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'desktopcouch/__init__.py'
--- desktopcouch/__init__.py 2009-08-04 11:15:52 +0000
+++ desktopcouch/__init__.py 2009-08-13 12:12:22 +0000
@@ -21,52 +21,66 @@
21import errno21import errno
22import time22import time
2323
24def find_pid():24def find_pid(start_if_not_running=True):
25 # Work out whether CouchDB is running by looking at its pid file25 # Work out whether CouchDB is running by looking at its pid file
26 from desktopcouch import local_files26 def get_pid():
27 pid = ''27 from desktopcouch import local_files
28 try:28 try:
29 fp = open(local_files.FILE_PID)29 pid_file = local_files.FILE_PID
30 pid = int(fp.read())30 with open(pid_file) as fp:
31 fp.close()31 try:
32 except IOError:32 return int(fp.read())
33 pass33 except ValueError:
34 return None
35 except IOError:
36 return None
3437
35 if not is_couchdb(pid):38 pid = get_pid()
39 if not process_is_couchdb(pid) and start_if_not_running:
36 # pidfile is stale40 # pidfile is stale
37 # start CouchDB by running the startup script41 # start CouchDB by running the startup script
38 print "Desktop CouchDB is not running; starting it."42 print "Desktop CouchDB is not running; starting it.",
39 from desktopcouch import start_local_couchdb43 from desktopcouch import start_local_couchdb
40 start_local_couchdb.start_couchdb()44 start_local_couchdb.start_couchdb()
41 time.sleep(2) # give the process a chance to start45 for timeout in xrange(1000):
46 pid = get_pid()
47 if process_is_couchdb(pid):
48 break
49 print ".",
50 time.sleep(0.1)
4251
43 # get the pid52 if process_is_couchdb(pid):
44 try:53 print " done."
45 with open(local_files.FILE_PID) as pid_file:54 else:
46 pid = int(pid_file.read().strip())55 print " failed."
47 except IOError, e:
48 if e.errno == ENOENT:
49 raise RuntimeError("desktop-couch not started")56 raise RuntimeError("desktop-couch not started")
50 else:57
51 raise
52 return pid58 return pid
5359
54def is_couchdb(pid):60def process_is_couchdb__linux(pid):
61 if not isinstance(pid, int):
62 return False
63
55 proc_dir = "/proc/%s" % (pid,)64 proc_dir = "/proc/%s" % (pid,)
5665
57 # check to make sure that the process still exists66 try:
58 if not os.path.isdir(proc_dir):67 # check to make sure it is actually a desktop-couch instance
59 return False68 with open(os.path.join(proc_dir, 'cmdline')) as cmd_file:
6069 cmd = cmd_file.read()
61 # check to make sure it is actually a desktop-couch instance70 if '/desktop-couch' not in cmd:
62 with open(os.path.join(proc_dir, 'cmdline')) as cmd_file:71 return False
63 cmd = cmd_file.read()72
64 if re.search('desktop-couch', cmd) is None:73 # make sure it's our process.
74 if not os.access(os.path.join(proc_dir, "mem"), os.W_OK):
75 return False
76
77 except IOError:
65 return False78 return False
6679
67 return True80 return True
6881
69def find_port(pid):82
83def find_port__linux(pid):
70 # Look in the CouchDB log to find the port number, someday.84 # Look in the CouchDB log to find the port number, someday.
71 # Currently, we have to grovel around in /proc instead.85 # Currently, we have to grovel around in /proc instead.
72 # Oh, the huge manatee... (this replaced an lsof shell recipe86 # Oh, the huge manatee... (this replaced an lsof shell recipe
@@ -76,8 +90,12 @@
76 90
77 # enumerate the process' file descriptors91 # enumerate the process' file descriptors
78 fd_dir = os.path.join(proc_dir, 'fd')92 fd_dir = os.path.join(proc_dir, 'fd')
79 fd_paths = [os.readlink(os.path.join(fd_dir, fd))93 try:
80 for fd in os.listdir(fd_dir)]94 fd_paths = [os.readlink(os.path.join(fd_dir, fd))
95 for fd in os.listdir(fd_dir)]
96 except OSError:
97 raise RuntimeError("Unable to find file descriptors in /proc")
98
8199
82 # identify socket fds100 # identify socket fds
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]
@@ -108,9 +126,24 @@
108 for line in tcp_file:126 for line in tcp_file:
109 match = listening_regexp.match(line)127 match = listening_regexp.match(line)
110 if match is not None:128 if match is not None:
111 port = int(match.group(1), 16)129 port = str(int(match.group(1), 16))
112 break130 break
113 if port is None:131 if port is None:
114 raise RuntimeError("Unable to find listening port")132 raise RuntimeError("Unable to find listening port")
115133
116 return str(port)134 return port
135
136
137
138import platform
139os_name = platform.system()
140try:
141 process_is_couchdb = {
142 "Linux":process_is_couchdb__linux
143 } [os_name]
144
145 find_port = {
146 "Linux":find_port__linux
147 } [os_name]
148except KeyError:
149 raise NotImplementedError("os %r is not yet supported" % (os_name,))
117150
=== modified file 'desktopcouch/records/tests/test_field_registry.py'
--- desktopcouch/records/tests/test_field_registry.py 2009-07-08 17:48:11 +0000
+++ desktopcouch/records/tests/test_field_registry.py 2009-08-13 16:06:20 +0000
@@ -18,7 +18,7 @@
18"""Test cases for field mapping"""18"""Test cases for field mapping"""
1919
20import copy20import copy
21from twisted.trial.unittest import TestCase as TwistedTestCase21from testtools import TestCase
22from desktopcouch.records.field_registry import (22from desktopcouch.records.field_registry import (
23 SimpleFieldMapping, MergeableListFieldMapping, Transformer)23 SimpleFieldMapping, MergeableListFieldMapping, Transformer)
24from desktopcouch.records.record import Record24from desktopcouch.records.record import Record
@@ -53,7 +53,7 @@
53 super(AppTransformer, self).__init__('Test App', field_registry)53 super(AppTransformer, self).__init__('Test App', field_registry)
5454
5555
56class TestFieldMapping(TwistedTestCase):56class TestFieldMapping(TestCase):
57 """Test Case for FieldMapping objects."""57 """Test Case for FieldMapping objects."""
5858
59 def setUp(self):59 def setUp(self):
@@ -85,7 +85,7 @@
85 self.assertEqual(None, mapping.getValue(record))85 self.assertEqual(None, mapping.getValue(record))
8686
8787
88class TestTransformer(TwistedTestCase):88class TestTransformer(TestCase):
89 """Test application specific transformer classes"""89 """Test application specific transformer classes"""
9090
91 def setUp(self):91 def setUp(self):
9292
=== modified file 'desktopcouch/records/tests/test_record.py'
--- desktopcouch/records/tests/test_record.py 2009-07-08 17:48:11 +0000
+++ desktopcouch/records/tests/test_record.py 2009-08-13 16:06:20 +0000
@@ -18,7 +18,7 @@
1818
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."""
2020
21from twisted.trial.unittest import TestCase as TwistedTestCase21from testtools import TestCase
2222
23# pylint does not like relative imports from containing packages23# pylint does not like relative imports from containing packages
24# pylint: disable-msg=F040124# pylint: disable-msg=F0401
@@ -26,7 +26,7 @@
26 record_factory, IllegalKeyException, validate)26 record_factory, IllegalKeyException, validate)
2727
2828
29class TestRecords(TwistedTestCase):29class TestRecords(TestCase):
30 """Test the record functionality"""30 """Test the record functionality"""
3131
32 def setUp(self):32 def setUp(self):
@@ -180,7 +180,7 @@
180 self.record.record_type)180 self.record.record_type)
181181
182182
183class TestRecordFactory(TwistedTestCase):183class TestRecordFactory(TestCase):
184 """Test Record/Mergeable List factories."""184 """Test Record/Mergeable List factories."""
185185
186 def setUp(self):186 def setUp(self):
187187
=== modified file 'desktopcouch/records/tests/test_server.py'
--- desktopcouch/records/tests/test_server.py 2009-08-10 21:32:52 +0000
+++ desktopcouch/records/tests/test_server.py 2009-08-12 14:26:40 +0000
@@ -19,7 +19,8 @@
19"""testing database/contact.py module"""19"""testing database/contact.py module"""
2020
21import testtools21import testtools
2222import random
23from desktopcouch.stop_local_couchdb import stop_couchdb
23from desktopcouch.records.server import CouchDatabase24from desktopcouch.records.server import CouchDatabase
24from desktopcouch.records.record import Record25from desktopcouch.records.record import Record
2526
@@ -49,6 +50,9 @@
49 def tearDown(self):50 def tearDown(self):
50 """tear down each test"""51 """tear down each test"""
51 del self.database._server[self.dbname]52 del self.database._server[self.dbname]
53 if random.choice([1,2,3,4]) == 3: # don't harass it unnecessarily
54 print u"\u2620", # death
55 stop_couchdb()
5256
53 def test_get_records_by_record_type_save_view(self):57 def test_get_records_by_record_type_save_view(self):
54 """Test getting mutliple records by type"""58 """Test getting mutliple records by type"""
5559
=== modified file 'desktopcouch/start_local_couchdb.py' (properties changed: -x to +x)
--- desktopcouch/start_local_couchdb.py 2009-07-27 18:16:19 +0000
+++ desktopcouch/start_local_couchdb.py 2009-08-13 12:14:05 +0000
@@ -32,10 +32,12 @@
32"""32"""
3333
34from __future__ import with_statement34from __future__ import with_statement
35import os, subprocess, sys35import os, sys
36import subprocess
36import desktopcouch37import desktopcouch
37from desktopcouch import local_files38from desktopcouch import local_files
38import xdg.BaseDirectory39import xdg.BaseDirectory
40import errno
39import time41import time
4042
41def dump_ini(data, filename):43def dump_ini(data, filename):
@@ -52,8 +54,6 @@
52 fd.write("\n")54 fd.write("\n")
53 fd.close()55 fd.close()
5456
55
56
57def create_ini_file():57def create_ini_file():
58 """Write CouchDB ini file if not already present"""58 """Write CouchDB ini file if not already present"""
59 # FIXME add update trigger folder59 # FIXME add update trigger folder
@@ -89,7 +89,16 @@
89 """Actually start the CouchDB process"""89 """Actually start the CouchDB process"""
90 local_exec = local_files.COUCH_EXEC_COMMAND + ['-b']90 local_exec = local_files.COUCH_EXEC_COMMAND + ['-b']
91 try:91 try:
92 retcode = subprocess.call(local_exec, shell=False)92 # subprocess is buggy. Chad patched, but that takes time to propagate.
93 proc = subprocess.Popen(local_exec)
94 while True:
95 try:
96 retcode = proc.wait()
97 break
98 except OSError, e:
99 if e.errno == errno.EINTR:
100 continue
101 raise
93 if retcode < 0:102 if retcode < 0:
94 print >> sys.stderr, "Child was terminated by signal", -retcode103 print >> sys.stderr, "Child was terminated by signal", -retcode
95 elif retcode > 0:104 elif retcode > 0:
@@ -118,15 +127,30 @@
118 html = fp.read()127 html = fp.read()
119 fp.close()128 fp.close()
120129
121 time.sleep(1)130 port = None
122 pid = desktopcouch.find_pid()131 for retry in xrange(10000, 0, -1):
123 port = desktopcouch.find_port(pid)132 pid = desktopcouch.find_pid(start_if_not_running=False)
133 try:
134 port = desktopcouch.find_port(pid)
135 break
136 except RuntimeError, e:
137 if retry == 1:
138 raise
139 time.sleep(0.01)
140 continue
124141
125 fp = open(bookmark_file, "w")142 if port is None:
126 fp.write(html.replace("[[COUCHDB_PORT]]", port))143 print "We couldn't find desktop-CouchDB's network port. Bookmark file not written."
127 fp.close()144 try:
128 print "Browse your desktop CouchDB at file://%s" % \145 os.remove(bookmark_file)
129 os.path.realpath(bookmark_file)146 except OSError:
147 pass
148 else:
149 fp = open(bookmark_file, "w")
150 fp.write(html.replace("[[COUCHDB_PORT]]", port))
151 fp.close()
152 print "Browse your desktop CouchDB at file://%s" % \
153 os.path.realpath(bookmark_file)
130154
131def start_couchdb():155def start_couchdb():
132 """Execute each step to start a desktop CouchDB"""156 """Execute each step to start a desktop CouchDB"""
@@ -135,6 +159,7 @@
135 update_design_documents()159 update_design_documents()
136 write_bookmark_file()160 write_bookmark_file()
137161
162
138if __name__ == "__main__":163if __name__ == "__main__":
139 start_couchdb()164 start_couchdb()
140 print "Desktop CouchDB started"165 print "Desktop CouchDB started"
141166
=== added file 'desktopcouch/stop_local_couchdb.py'
--- desktopcouch/stop_local_couchdb.py 1970-01-01 00:00:00 +0000
+++ desktopcouch/stop_local_couchdb.py 2009-08-12 14:26:14 +0000
@@ -0,0 +1,49 @@
1#!/usr/bin/python
2# Copyright 2009 Canonical Ltd.
3#
4# This file is part of desktopcouch.
5#
6# desktopcouch is free software: you can redistribute it and/or modify
7# it under the terms of the GNU Lesser General Public License version 3
8# as published by the Free Software Foundation.
9#
10# desktopcouch is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public License
16# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
17#
18# Author: Chad Miller <chad.miller@canonical.com>
19"""
20Stop local CouchDB server.
21"""
22
23import os
24import desktopcouch
25import time
26import signal
27import errno
28
29def stop_couchdb():
30 pid = desktopcouch.find_pid(start_if_not_running=False)
31 while pid is not None:
32 try:
33 os.kill(pid, signal.SIGTERM)
34 except OSError, e:
35 if e.errno == errno.ESRCH:
36 break
37 raise
38
39 for retry in xrange(300):
40 try:
41 os.kill(pid, 0) # test existence. sig-zero is special.
42 except OSError:
43 break
44 time.sleep(0.01)
45
46 pid = desktopcouch.find_pid(start_if_not_running=False)
47
48if __name__ == "__main__":
49 stop_couchdb()

Subscribers

People subscribed via source and target branches