Merge lp:~soren/nova/cross-binary-sync into lp:~hudson-openstack/nova/trunk

Proposed by Soren Hansen
Status: Merged
Approved by: Soren Hansen
Approved revision: 754
Merged at revision: 773
Proposed branch: lp:~soren/nova/cross-binary-sync
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 139 lines (+67/-3)
3 files modified
nova/flags.py (+2/-0)
nova/tests/test_misc.py (+47/-1)
nova/utils.py (+18/-2)
To merge this branch: bzr merge lp:~soren/nova/cross-binary-sync
Reviewer Review Type Date Requested Status
Josh Kearney (community) Approve
Rick Harris (community) Needs Information
Vish Ishaya (community) Approve
Jay Pipes (community) Approve
Review via email: mp+51533@code.launchpad.net

Description of the change

Add a decorator that lets you synchronise actions across multiple binaries. Like, say, ensuring that only one worker manipulates iptables at a time.

To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote :

How would this work if the binaries are on multiple hosts?

review: Needs Information
Revision history for this message
Vish Ishaya (vishvananda) wrote :

If they are on multiple hosts they don't need to be synced. It is to avoid
two processes changing iptables rules on the same host.
On Feb 28, 2011 9:26 PM, "Jay Pipes" <email address hidden> wrote:

Revision history for this message
Jay Pipes (jaypipes) wrote :

> If they are on multiple hosts they don't need to be synced. It is to avoid
> two processes changing iptables rules on the same host.

OK, thx for the answer.

review: Approve
Revision history for this message
Rick Harris (rconradharris) wrote :

lgtm, though, I think it would ease debugging if we copied the inner-function's metadata out to the wrapper-function.

We can use

   @functools.wraps(f)
   def inner(*args, **kwargs):

to do that.

Revision history for this message
Soren Hansen (soren) wrote :

> lgtm, though, I think it would ease debugging if we copied the inner-
> function's metadata out to the wrapper-function.
>
> We can use
>
> @functools.wraps(f)
> def inner(*args, **kwargs):
>
> to do that.

Good suggestion, thanks. Doing so.

Revision history for this message
Vish Ishaya (vishvananda) wrote :

Hey Soren. The syncronization test passes just fine normally, but seems to fail when run as part of a build. Any idea why that might be happening?

review: Needs Information
Revision history for this message
Vish Ishaya (vishvananda) wrote :

Hmm Ignore that. It started working. I don't know what was causing the issue. The only change i made was fixing the rename of nova-api.conf

review: Approve
Revision history for this message
Rick Harris (rconradharris) wrote :

I'm getting sporadic failures. I wrote a little test-harness to make 10 attempts, had 70% fail.

Success: 3
Failed: 7
Total Attempts 10
rick@maverick:~/openstack/nova/cross-binary-sync$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=10.10
DISTRIB_CODENAME=maverick
DISTRIB_DESCRIPTION="Ubuntu 10.10"

This is the (unhelpful) traceback: http://paste.openstack.org/show/833/

Haven't really dug in yet.

review: Needs Information
Revision history for this message
Soren Hansen (soren) wrote :

Ironically, the tests were racy. They depended on one of the processes to be executed first. For some reason they only failed for me roughly 1 in 10-12 times. Thanks for pointing this out.

I refactored the tests to make it work regardless of the order of execution.

Revision history for this message
Josh Kearney (jk0) wrote :

Tests are working as expected.

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :

The attempt to merge lp:~soren/nova/cross-binary-sync into lp:nova failed. Below is the output from the failed tests.

Traceback (most recent call last):
  File "run_tests.py", line 68, in <module>
    from nova.tests import fake_flags
  File "/tmp/tmpHVsZF0/nova/tests/fake_flags.py", line 23, in <module>
    flags.DECLARE('volume_driver', 'nova.volume.manager')
  File "/tmp/tmpHVsZF0/nova/flags.py", line 236, in DECLARE
    __import__(module_string, globals(), locals())
  File "/tmp/tmpHVsZF0/nova/volume/__init__.py", line 19, in <module>
    from nova.volume.api import API
  File "/tmp/tmpHVsZF0/nova/volume/api.py", line 25, in <module>
    from nova import db
  File "/tmp/tmpHVsZF0/nova/db/__init__.py", line 23, in <module>
    from nova.db.api import *
  File "/tmp/tmpHVsZF0/nova/db/api.py", line 37, in <module>
    from nova import utils
  File "/tmp/tmpHVsZF0/nova/utils.py", line 29, in <module>
    import lockfile
ImportError: No module named lockfile

Revision history for this message
Soren Hansen (soren) wrote :

Installed lockfile on the Hudson box. I'm surprised it wasn't there already. We used to use lockfile back in the very old days (bzr revno 1 has references to it). Meh.

Revision history for this message
Jay Pipes (jaypipes) wrote :

It was only used in the server daemons. And, guess what, none of those
are tested in the unit tests.

On Wed, Mar 9, 2011 at 3:05 PM, Soren Hansen <email address hidden> wrote:
> Installed lockfile on the Hudson box. I'm surprised it wasn't there already. We used to use lockfile back in the very old days (bzr revno 1 has references to it). Meh.
> --
> https://code.launchpad.net/~soren/nova/cross-binary-sync/+merge/51533
> You are reviewing the proposed merge of lp:~soren/nova/cross-binary-sync into lp:nova.
>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/flags.py'
2--- nova/flags.py 2011-02-25 01:04:25 +0000
3+++ nova/flags.py 2011-03-09 09:38:12 +0000
4@@ -321,6 +321,8 @@
5
6 DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'),
7 "Top-level directory for maintaining nova's state")
8+DEFINE_string('lock_path', os.path.join(os.path.dirname(__file__), '../'),
9+ "Directory for lock files")
10 DEFINE_string('logdir', None, 'output to a per-service log file in named '
11 'directory')
12
13
14=== modified file 'nova/tests/test_misc.py'
15--- nova/tests/test_misc.py 2011-02-21 18:55:25 +0000
16+++ nova/tests/test_misc.py 2011-03-09 09:38:12 +0000
17@@ -14,10 +14,12 @@
18 # License for the specific language governing permissions and limitations
19 # under the License.
20
21+import errno
22 import os
23+import select
24
25 from nova import test
26-from nova.utils import parse_mailmap, str_dict_replace
27+from nova.utils import parse_mailmap, str_dict_replace, synchronized
28
29
30 class ProjectTestCase(test.TestCase):
31@@ -55,3 +57,47 @@
32 '%r not listed in Authors' % missing)
33 finally:
34 tree.unlock()
35+
36+
37+class LockTestCase(test.TestCase):
38+ def test_synchronized_wrapped_function_metadata(self):
39+ @synchronized('whatever')
40+ def foo():
41+ """Bar"""
42+ pass
43+ self.assertEquals(foo.__doc__, 'Bar', "Wrapped function's docstring "
44+ "got lost")
45+ self.assertEquals(foo.__name__, 'foo', "Wrapped function's name "
46+ "got mangled")
47+
48+ def test_synchronized(self):
49+ rpipe1, wpipe1 = os.pipe()
50+ rpipe2, wpipe2 = os.pipe()
51+
52+ @synchronized('testlock')
53+ def f(rpipe, wpipe):
54+ try:
55+ os.write(wpipe, "foo")
56+ except OSError, e:
57+ self.assertEquals(e.errno, errno.EPIPE)
58+ return
59+
60+ rfds, _, __ = select.select([rpipe], [], [], 1)
61+ self.assertEquals(len(rfds), 0, "The other process, which was"
62+ " supposed to be locked, "
63+ "wrote on its end of the "
64+ "pipe")
65+ os.close(rpipe)
66+
67+ pid = os.fork()
68+ if pid > 0:
69+ os.close(wpipe1)
70+ os.close(rpipe2)
71+
72+ f(rpipe1, wpipe2)
73+ else:
74+ os.close(rpipe1)
75+ os.close(wpipe2)
76+
77+ f(rpipe2, wpipe1)
78+ os._exit(0)
79
80=== modified file 'nova/utils.py'
81--- nova/utils.py 2011-02-23 22:50:33 +0000
82+++ nova/utils.py 2011-03-09 09:38:12 +0000
83@@ -23,10 +23,14 @@
84
85 import base64
86 import datetime
87+import functools
88 import inspect
89 import json
90+import lockfile
91+import netaddr
92 import os
93 import random
94+import re
95 import socket
96 import string
97 import struct
98@@ -34,8 +38,6 @@
99 import time
100 import types
101 from xml.sax import saxutils
102-import re
103-import netaddr
104
105 from eventlet import event
106 from eventlet import greenthread
107@@ -43,11 +45,13 @@
108
109 from nova import exception
110 from nova.exception import ProcessExecutionError
111+from nova import flags
112 from nova import log as logging
113
114
115 LOG = logging.getLogger("nova.utils")
116 TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
117+FLAGS = flags.FLAGS
118
119
120 def import_class(import_str):
121@@ -491,6 +495,18 @@
122 return json.loads(s)
123
124
125+def synchronized(name):
126+ def wrap(f):
127+ @functools.wraps(f)
128+ def inner(*args, **kwargs):
129+ lock = lockfile.FileLock(os.path.join(FLAGS.lock_path,
130+ 'nova-%s.lock' % name))
131+ with lock:
132+ return f(*args, **kwargs)
133+ return inner
134+ return wrap
135+
136+
137 def ensure_b64_encoding(val):
138 """Safety method to ensure that values expected to be base64-encoded
139 actually are. If they are, the value is returned unchanged. Otherwise,