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
=== modified file 'nova/flags.py'
--- nova/flags.py 2011-02-25 01:04:25 +0000
+++ nova/flags.py 2011-03-09 09:38:12 +0000
@@ -321,6 +321,8 @@
321321
322DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'),322DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'),
323 "Top-level directory for maintaining nova's state")323 "Top-level directory for maintaining nova's state")
324DEFINE_string('lock_path', os.path.join(os.path.dirname(__file__), '../'),
325 "Directory for lock files")
324DEFINE_string('logdir', None, 'output to a per-service log file in named '326DEFINE_string('logdir', None, 'output to a per-service log file in named '
325 'directory')327 'directory')
326328
327329
=== modified file 'nova/tests/test_misc.py'
--- nova/tests/test_misc.py 2011-02-21 18:55:25 +0000
+++ nova/tests/test_misc.py 2011-03-09 09:38:12 +0000
@@ -14,10 +14,12 @@
14# License for the specific language governing permissions and limitations14# License for the specific language governing permissions and limitations
15# under the License.15# under the License.
1616
17import errno
17import os18import os
19import select
1820
19from nova import test21from nova import test
20from nova.utils import parse_mailmap, str_dict_replace22from nova.utils import parse_mailmap, str_dict_replace, synchronized
2123
2224
23class ProjectTestCase(test.TestCase):25class ProjectTestCase(test.TestCase):
@@ -55,3 +57,47 @@
55 '%r not listed in Authors' % missing)57 '%r not listed in Authors' % missing)
56 finally:58 finally:
57 tree.unlock()59 tree.unlock()
60
61
62class LockTestCase(test.TestCase):
63 def test_synchronized_wrapped_function_metadata(self):
64 @synchronized('whatever')
65 def foo():
66 """Bar"""
67 pass
68 self.assertEquals(foo.__doc__, 'Bar', "Wrapped function's docstring "
69 "got lost")
70 self.assertEquals(foo.__name__, 'foo', "Wrapped function's name "
71 "got mangled")
72
73 def test_synchronized(self):
74 rpipe1, wpipe1 = os.pipe()
75 rpipe2, wpipe2 = os.pipe()
76
77 @synchronized('testlock')
78 def f(rpipe, wpipe):
79 try:
80 os.write(wpipe, "foo")
81 except OSError, e:
82 self.assertEquals(e.errno, errno.EPIPE)
83 return
84
85 rfds, _, __ = select.select([rpipe], [], [], 1)
86 self.assertEquals(len(rfds), 0, "The other process, which was"
87 " supposed to be locked, "
88 "wrote on its end of the "
89 "pipe")
90 os.close(rpipe)
91
92 pid = os.fork()
93 if pid > 0:
94 os.close(wpipe1)
95 os.close(rpipe2)
96
97 f(rpipe1, wpipe2)
98 else:
99 os.close(rpipe1)
100 os.close(wpipe2)
101
102 f(rpipe2, wpipe1)
103 os._exit(0)
58104
=== modified file 'nova/utils.py'
--- nova/utils.py 2011-02-23 22:50:33 +0000
+++ nova/utils.py 2011-03-09 09:38:12 +0000
@@ -23,10 +23,14 @@
2323
24import base6424import base64
25import datetime25import datetime
26import functools
26import inspect27import inspect
27import json28import json
29import lockfile
30import netaddr
28import os31import os
29import random32import random
33import re
30import socket34import socket
31import string35import string
32import struct36import struct
@@ -34,8 +38,6 @@
34import time38import time
35import types39import types
36from xml.sax import saxutils40from xml.sax import saxutils
37import re
38import netaddr
3941
40from eventlet import event42from eventlet import event
41from eventlet import greenthread43from eventlet import greenthread
@@ -43,11 +45,13 @@
4345
44from nova import exception46from nova import exception
45from nova.exception import ProcessExecutionError47from nova.exception import ProcessExecutionError
48from nova import flags
46from nova import log as logging49from nova import log as logging
4750
4851
49LOG = logging.getLogger("nova.utils")52LOG = logging.getLogger("nova.utils")
50TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"53TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
54FLAGS = flags.FLAGS
5155
5256
53def import_class(import_str):57def import_class(import_str):
@@ -491,6 +495,18 @@
491 return json.loads(s)495 return json.loads(s)
492496
493497
498def synchronized(name):
499 def wrap(f):
500 @functools.wraps(f)
501 def inner(*args, **kwargs):
502 lock = lockfile.FileLock(os.path.join(FLAGS.lock_path,
503 'nova-%s.lock' % name))
504 with lock:
505 return f(*args, **kwargs)
506 return inner
507 return wrap
508
509
494def ensure_b64_encoding(val):510def ensure_b64_encoding(val):
495 """Safety method to ensure that values expected to be base64-encoded511 """Safety method to ensure that values expected to be base64-encoded
496 actually are. If they are, the value is returned unchanged. Otherwise,512 actually are. If they are, the value is returned unchanged. Otherwise,