Merge lp:~cbehrens/nova/lp766404 into lp:~hudson-openstack/nova/trunk

Proposed by Chris Behrens
Status: Merged
Approved by: Josh Kearney
Approved revision: 1067
Merged at revision: 1070
Proposed branch: lp:~cbehrens/nova/lp766404
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 234 lines (+76/-74)
4 files modified
nova/tests/test_xenapi.py (+23/-0)
nova/tests/xenapi/stubs.py (+11/-28)
nova/utils.py (+2/-0)
nova/virt/xenapi_conn.py (+40/-46)
To merge this branch: bzr merge lp:~cbehrens/nova/lp766404
Reviewer Review Type Date Requested Status
Vish Ishaya (community) Approve
Josh Kearney (community) Approve
Review via email: mp+60543@code.launchpad.net

Description of the change

XenAPI was not implemented to allow for multiple simultaneous XenAPI requests. A single XenAPIConnection (and thus XenAPISession) is used for all queries. XenAPISession's wait_for_task method would set a self.loop = for looping calls to _poll_task until task completion. Subsequent (parallel) calls to wait_for_task for another query would overwrite this. XenAPISession._poll_task was pulled into the XenAPISession.wait_for_task method to avoid having to store self.loop.

To post a comment you must log in.
Revision history for this message
Brian Lamar (blamar) wrote :

Are there tests that can show this fix in action?

Revision history for this message
Chris Behrens (cbehrens) wrote :

Good question. There are XenAPI tests in general which exercise this code (nova/tests/xenapi/*). I don't think I'm going to easily be able to get 2 queries going at once in the tests due to how quickly they execute. Hmm.

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

You can probably get this to work with a greenthread and a mock method that uses time.sleep. If you want to get a little bit more elaborate you can make a method that waits on an eventlet Event() so you can be sure that it pauses until you tell it to finish. See CacheConcurrencyTestCase in test_virt for an example.

On May 11, 2011, at 10:31 AM, Chris Behrens wrote:

> Good question. There are XenAPI tests in general which exercise this code (nova/tests/xenapi/*). I don't think I'm going to easily be able to get 2 queries going at once in the tests due to how quickly they execute. Hmm.
> --
> https://code.launchpad.net/~cbehrens/nova/lp766404/+merge/60543
> You are subscribed to branch lp:nova.

Revision history for this message
Chris Behrens (cbehrens) wrote :

Ya, was thinking along those lines. I'll take a look at that example. Thnx!

Revision history for this message
Chris Behrens (cbehrens) wrote :

Ok, got a test in that fails before fixing this bug and succeeds after fixing. Also added a small improvement to LoopingCall to not sleep unnecessarily.

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

Looks great and tests perform as expected! Very nice fix.

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

lgtm

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :
Download full text (51.1 KiB)

The attempt to merge lp:~cbehrens/nova/lp766404 into lp:nova failed. Below is the output from the failed tests.

AccountsTest
    test_account_create OK
    test_account_delete OK
    test_account_update OK
    test_get_account OK
AdminAPITest
    test_admin_disabled OK
    test_admin_enabled OK
APITest
    test_exceptions_are_converted_to_faults OK
Test
    test_authorize_token OK
    test_authorize_user OK
    test_bad_token OK
    test_bad_user_bad_key OK
    test_bad_user_good_key OK
    test_no_user OK
    test_token_expiry OK
TestFunctional
    test_token_doesnotexist OK
    test_token_expiry OK
TestLimiter
    test_authorize_token OK
LimiterTest
    test_limiter_custom_max_limit OK
    test_limiter_limit_and_offset OK
    test_limiter_limit_medium OK
    test_limiter_limit_over_max OK
    test_limiter_limit_zero OK
    test_limiter_negative_limit OK
    test_limiter_negative_offset OK
    test_limiter_nothing OK
    test_limiter_offset_bad OK
    test_limiter_offset_blank OK
    test_limiter_offset_medium OK
    test_limiter_offset_over_max OK
    test_limiter_offset_zero OK
ActionExtensionTest
    test_extended_action OK
    test_invalid_action OK
    test_invalid_action_body OK
ExtensionControllerTest
    test_get_by_alias OK
    test_index OK
ExtensionManagerTest
    test_get_resources OK
ResourceExtensionTest
    test_get_resources OK
    test_get_resources_with_controller OK
    test_no_extension_present OK
ResponseExtensionTest
    test_get_resources_with_mgr OK
    test_get_resources_with_stub_mgr OK
TestFaults
    test_400_fault_json OK
    test_400_fault_xml OK
    test...

Revision history for this message
Chris Behrens (cbehrens) wrote :

Fixed the pep8 issues with the tests.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/tests/test_xenapi.py'
2--- nova/tests/test_xenapi.py 2011-05-09 19:57:56 +0000
3+++ nova/tests/test_xenapi.py 2011-05-13 16:47:26 +0000
4@@ -16,6 +16,7 @@
5
6 """Test suite for XenAPI."""
7
8+import eventlet
9 import functools
10 import json
11 import os
12@@ -198,6 +199,28 @@
13 self.context = context.RequestContext('fake', 'fake', False)
14 self.conn = xenapi_conn.get_connection(False)
15
16+ def test_parallel_builds(self):
17+ stubs.stubout_loopingcall_delay(self.stubs)
18+
19+ def _do_build(id, proj, user, *args):
20+ values = {
21+ 'id': id,
22+ 'project_id': proj,
23+ 'user_id': user,
24+ 'image_id': 1,
25+ 'kernel_id': 2,
26+ 'ramdisk_id': 3,
27+ 'instance_type_id': '3', # m1.large
28+ 'mac_address': 'aa:bb:cc:dd:ee:ff',
29+ 'os_type': 'linux'}
30+ instance = db.instance_create(self.context, values)
31+ self.conn.spawn(instance)
32+
33+ gt1 = eventlet.spawn(_do_build, 1, self.project.id, self.user.id)
34+ gt2 = eventlet.spawn(_do_build, 2, self.project.id, self.user.id)
35+ gt1.wait()
36+ gt2.wait()
37+
38 def test_list_instances_0(self):
39 instances = self.conn.list_instances()
40 self.assertEquals(instances, [])
41
42=== modified file 'nova/tests/xenapi/stubs.py'
43--- nova/tests/xenapi/stubs.py 2011-03-25 23:43:21 +0000
44+++ nova/tests/xenapi/stubs.py 2011-05-13 16:47:26 +0000
45@@ -16,6 +16,7 @@
46
47 """Stubouts, mocks and fixtures for the test suite"""
48
49+import eventlet
50 from nova.virt import xenapi_conn
51 from nova.virt.xenapi import fake
52 from nova.virt.xenapi import volume_utils
53@@ -28,29 +29,6 @@
54 @classmethod
55 def fake_fetch_image(cls, session, instance_id, image, user, project,
56 type):
57- # Stubout wait_for_task
58- def fake_wait_for_task(self, task, id):
59- class FakeEvent:
60-
61- def send(self, value):
62- self.rv = value
63-
64- def wait(self):
65- return self.rv
66-
67- done = FakeEvent()
68- self._poll_task(id, task, done)
69- rv = done.wait()
70- return rv
71-
72- def fake_loop(self):
73- pass
74-
75- stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task',
76- fake_wait_for_task)
77-
78- stubs.Set(xenapi_conn.XenAPISession, '_stop_loop', fake_loop)
79-
80 from nova.virt.xenapi.fake import create_vdi
81 name_label = "instance-%s" % instance_id
82 #TODO: create fake SR record
83@@ -62,11 +40,6 @@
84 return vdi_uuid
85
86 stubs.Set(vm_utils.VMHelper, 'fetch_image', fake_fetch_image)
87-
88- def fake_parse_xmlrpc_value(val):
89- return val
90-
91- stubs.Set(xenapi_conn, '_parse_xmlrpc_value', fake_parse_xmlrpc_value)
92
93 def fake_wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref,
94 original_parent_uuid):
95@@ -144,6 +117,16 @@
96 stubs.Set(utils.LoopingCall, 'start', fake_start)
97
98
99+def stubout_loopingcall_delay(stubs):
100+ def fake_start(self, interval, now=True):
101+ self._running = True
102+ eventlet.sleep(1)
103+ self.f(*self.args, **self.kw)
104+ # This would fail before parallel xenapi calls were fixed
105+ assert self._running == False
106+ stubs.Set(utils.LoopingCall, 'start', fake_start)
107+
108+
109 class FakeSessionForVMTests(fake.SessionBase):
110 """ Stubs out a XenAPISession for VM tests """
111 def __init__(self, uri):
112
113=== modified file 'nova/utils.py'
114--- nova/utils.py 2011-05-11 22:56:57 +0000
115+++ nova/utils.py 2011-05-13 16:47:26 +0000
116@@ -462,6 +462,8 @@
117 try:
118 while self._running:
119 self.f(*self.args, **self.kw)
120+ if not self._running:
121+ break
122 greenthread.sleep(interval)
123 except LoopingCallDone, e:
124 self.stop()
125
126=== modified file 'nova/virt/xenapi_conn.py'
127--- nova/virt/xenapi_conn.py 2011-05-09 19:57:56 +0000
128+++ nova/virt/xenapi_conn.py 2011-05-13 16:47:26 +0000
129@@ -347,7 +347,6 @@
130 "(is the Dom0 disk full?)"))
131 with timeout.Timeout(FLAGS.xenapi_login_timeout, exception):
132 self._session.login_with_password(user, pw)
133- self.loop = None
134
135 def get_imported_xenapi(self):
136 """Stubout point. This can be replaced with a mock xenapi module."""
137@@ -384,57 +383,52 @@
138
139 def wait_for_task(self, task, id=None):
140 """Return the result of the given task. The task is polled
141- until it completes. Not re-entrant."""
142+ until it completes."""
143 done = event.Event()
144- self.loop = utils.LoopingCall(self._poll_task, id, task, done)
145- self.loop.start(FLAGS.xenapi_task_poll_interval, now=True)
146- rv = done.wait()
147- self.loop.stop()
148- return rv
149-
150- def _stop_loop(self):
151- """Stop polling for task to finish."""
152- #NOTE(sandy-walsh) Had to break this call out to support unit tests.
153- if self.loop:
154- self.loop.stop()
155+ loop = utils.LoopingCall(f=None)
156+
157+ def _poll_task():
158+ """Poll the given XenAPI task, and return the result if the
159+ action was completed successfully or not.
160+ """
161+ try:
162+ name = self._session.xenapi.task.get_name_label(task)
163+ status = self._session.xenapi.task.get_status(task)
164+ if id:
165+ action = dict(
166+ instance_id=int(id),
167+ action=name[0:255], # Ensure action is never > 255
168+ error=None)
169+ if status == "pending":
170+ return
171+ elif status == "success":
172+ result = self._session.xenapi.task.get_result(task)
173+ LOG.info(_("Task [%(name)s] %(task)s status:"
174+ " success %(result)s") % locals())
175+ done.send(_parse_xmlrpc_value(result))
176+ else:
177+ error_info = self._session.xenapi.task.get_error_info(task)
178+ action["error"] = str(error_info)
179+ LOG.warn(_("Task [%(name)s] %(task)s status:"
180+ " %(status)s %(error_info)s") % locals())
181+ done.send_exception(self.XenAPI.Failure(error_info))
182+
183+ if id:
184+ db.instance_action_create(context.get_admin_context(),
185+ action)
186+ except self.XenAPI.Failure, exc:
187+ LOG.warn(exc)
188+ done.send_exception(*sys.exc_info())
189+ loop.stop()
190+
191+ loop.f = _poll_task
192+ loop.start(FLAGS.xenapi_task_poll_interval, now=True)
193+ return done.wait()
194
195 def _create_session(self, url):
196 """Stubout point. This can be replaced with a mock session."""
197 return self.XenAPI.Session(url)
198
199- def _poll_task(self, id, task, done):
200- """Poll the given XenAPI task, and fire the given action if we
201- get a result.
202- """
203- try:
204- name = self._session.xenapi.task.get_name_label(task)
205- status = self._session.xenapi.task.get_status(task)
206- if id:
207- action = dict(
208- instance_id=int(id),
209- action=name[0:255], # Ensure action is never > 255
210- error=None)
211- if status == "pending":
212- return
213- elif status == "success":
214- result = self._session.xenapi.task.get_result(task)
215- LOG.info(_("Task [%(name)s] %(task)s status:"
216- " success %(result)s") % locals())
217- done.send(_parse_xmlrpc_value(result))
218- else:
219- error_info = self._session.xenapi.task.get_error_info(task)
220- action["error"] = str(error_info)
221- LOG.warn(_("Task [%(name)s] %(task)s status:"
222- " %(status)s %(error_info)s") % locals())
223- done.send_exception(self.XenAPI.Failure(error_info))
224-
225- if id:
226- db.instance_action_create(context.get_admin_context(), action)
227- except self.XenAPI.Failure, exc:
228- LOG.warn(exc)
229- done.send_exception(*sys.exc_info())
230- self._stop_loop()
231-
232 def _unwrap_plugin_exceptions(self, func, *args, **kwargs):
233 """Parse exception details"""
234 try: