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
=== modified file 'nova/tests/test_xenapi.py'
--- nova/tests/test_xenapi.py 2011-05-09 19:57:56 +0000
+++ nova/tests/test_xenapi.py 2011-05-13 16:47:26 +0000
@@ -16,6 +16,7 @@
1616
17"""Test suite for XenAPI."""17"""Test suite for XenAPI."""
1818
19import eventlet
19import functools20import functools
20import json21import json
21import os22import os
@@ -198,6 +199,28 @@
198 self.context = context.RequestContext('fake', 'fake', False)199 self.context = context.RequestContext('fake', 'fake', False)
199 self.conn = xenapi_conn.get_connection(False)200 self.conn = xenapi_conn.get_connection(False)
200201
202 def test_parallel_builds(self):
203 stubs.stubout_loopingcall_delay(self.stubs)
204
205 def _do_build(id, proj, user, *args):
206 values = {
207 'id': id,
208 'project_id': proj,
209 'user_id': user,
210 'image_id': 1,
211 'kernel_id': 2,
212 'ramdisk_id': 3,
213 'instance_type_id': '3', # m1.large
214 'mac_address': 'aa:bb:cc:dd:ee:ff',
215 'os_type': 'linux'}
216 instance = db.instance_create(self.context, values)
217 self.conn.spawn(instance)
218
219 gt1 = eventlet.spawn(_do_build, 1, self.project.id, self.user.id)
220 gt2 = eventlet.spawn(_do_build, 2, self.project.id, self.user.id)
221 gt1.wait()
222 gt2.wait()
223
201 def test_list_instances_0(self):224 def test_list_instances_0(self):
202 instances = self.conn.list_instances()225 instances = self.conn.list_instances()
203 self.assertEquals(instances, [])226 self.assertEquals(instances, [])
204227
=== modified file 'nova/tests/xenapi/stubs.py'
--- nova/tests/xenapi/stubs.py 2011-03-25 23:43:21 +0000
+++ nova/tests/xenapi/stubs.py 2011-05-13 16:47:26 +0000
@@ -16,6 +16,7 @@
1616
17"""Stubouts, mocks and fixtures for the test suite"""17"""Stubouts, mocks and fixtures for the test suite"""
1818
19import eventlet
19from nova.virt import xenapi_conn20from nova.virt import xenapi_conn
20from nova.virt.xenapi import fake21from nova.virt.xenapi import fake
21from nova.virt.xenapi import volume_utils22from nova.virt.xenapi import volume_utils
@@ -28,29 +29,6 @@
28 @classmethod29 @classmethod
29 def fake_fetch_image(cls, session, instance_id, image, user, project,30 def fake_fetch_image(cls, session, instance_id, image, user, project,
30 type):31 type):
31 # Stubout wait_for_task
32 def fake_wait_for_task(self, task, id):
33 class FakeEvent:
34
35 def send(self, value):
36 self.rv = value
37
38 def wait(self):
39 return self.rv
40
41 done = FakeEvent()
42 self._poll_task(id, task, done)
43 rv = done.wait()
44 return rv
45
46 def fake_loop(self):
47 pass
48
49 stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task',
50 fake_wait_for_task)
51
52 stubs.Set(xenapi_conn.XenAPISession, '_stop_loop', fake_loop)
53
54 from nova.virt.xenapi.fake import create_vdi32 from nova.virt.xenapi.fake import create_vdi
55 name_label = "instance-%s" % instance_id33 name_label = "instance-%s" % instance_id
56 #TODO: create fake SR record34 #TODO: create fake SR record
@@ -62,11 +40,6 @@
62 return vdi_uuid40 return vdi_uuid
6341
64 stubs.Set(vm_utils.VMHelper, 'fetch_image', fake_fetch_image)42 stubs.Set(vm_utils.VMHelper, 'fetch_image', fake_fetch_image)
65
66 def fake_parse_xmlrpc_value(val):
67 return val
68
69 stubs.Set(xenapi_conn, '_parse_xmlrpc_value', fake_parse_xmlrpc_value)
7043
71 def fake_wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref,44 def fake_wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref,
72 original_parent_uuid):45 original_parent_uuid):
@@ -144,6 +117,16 @@
144 stubs.Set(utils.LoopingCall, 'start', fake_start)117 stubs.Set(utils.LoopingCall, 'start', fake_start)
145118
146119
120def stubout_loopingcall_delay(stubs):
121 def fake_start(self, interval, now=True):
122 self._running = True
123 eventlet.sleep(1)
124 self.f(*self.args, **self.kw)
125 # This would fail before parallel xenapi calls were fixed
126 assert self._running == False
127 stubs.Set(utils.LoopingCall, 'start', fake_start)
128
129
147class FakeSessionForVMTests(fake.SessionBase):130class FakeSessionForVMTests(fake.SessionBase):
148 """ Stubs out a XenAPISession for VM tests """131 """ Stubs out a XenAPISession for VM tests """
149 def __init__(self, uri):132 def __init__(self, uri):
150133
=== modified file 'nova/utils.py'
--- nova/utils.py 2011-05-11 22:56:57 +0000
+++ nova/utils.py 2011-05-13 16:47:26 +0000
@@ -462,6 +462,8 @@
462 try:462 try:
463 while self._running:463 while self._running:
464 self.f(*self.args, **self.kw)464 self.f(*self.args, **self.kw)
465 if not self._running:
466 break
465 greenthread.sleep(interval)467 greenthread.sleep(interval)
466 except LoopingCallDone, e:468 except LoopingCallDone, e:
467 self.stop()469 self.stop()
468470
=== modified file 'nova/virt/xenapi_conn.py'
--- nova/virt/xenapi_conn.py 2011-05-09 19:57:56 +0000
+++ nova/virt/xenapi_conn.py 2011-05-13 16:47:26 +0000
@@ -347,7 +347,6 @@
347 "(is the Dom0 disk full?)"))347 "(is the Dom0 disk full?)"))
348 with timeout.Timeout(FLAGS.xenapi_login_timeout, exception):348 with timeout.Timeout(FLAGS.xenapi_login_timeout, exception):
349 self._session.login_with_password(user, pw)349 self._session.login_with_password(user, pw)
350 self.loop = None
351350
352 def get_imported_xenapi(self):351 def get_imported_xenapi(self):
353 """Stubout point. This can be replaced with a mock xenapi module."""352 """Stubout point. This can be replaced with a mock xenapi module."""
@@ -384,57 +383,52 @@
384383
385 def wait_for_task(self, task, id=None):384 def wait_for_task(self, task, id=None):
386 """Return the result of the given task. The task is polled385 """Return the result of the given task. The task is polled
387 until it completes. Not re-entrant."""386 until it completes."""
388 done = event.Event()387 done = event.Event()
389 self.loop = utils.LoopingCall(self._poll_task, id, task, done)388 loop = utils.LoopingCall(f=None)
390 self.loop.start(FLAGS.xenapi_task_poll_interval, now=True)389
391 rv = done.wait()390 def _poll_task():
392 self.loop.stop()391 """Poll the given XenAPI task, and return the result if the
393 return rv392 action was completed successfully or not.
394393 """
395 def _stop_loop(self):394 try:
396 """Stop polling for task to finish."""395 name = self._session.xenapi.task.get_name_label(task)
397 #NOTE(sandy-walsh) Had to break this call out to support unit tests.396 status = self._session.xenapi.task.get_status(task)
398 if self.loop:397 if id:
399 self.loop.stop()398 action = dict(
399 instance_id=int(id),
400 action=name[0:255], # Ensure action is never > 255
401 error=None)
402 if status == "pending":
403 return
404 elif status == "success":
405 result = self._session.xenapi.task.get_result(task)
406 LOG.info(_("Task [%(name)s] %(task)s status:"
407 " success %(result)s") % locals())
408 done.send(_parse_xmlrpc_value(result))
409 else:
410 error_info = self._session.xenapi.task.get_error_info(task)
411 action["error"] = str(error_info)
412 LOG.warn(_("Task [%(name)s] %(task)s status:"
413 " %(status)s %(error_info)s") % locals())
414 done.send_exception(self.XenAPI.Failure(error_info))
415
416 if id:
417 db.instance_action_create(context.get_admin_context(),
418 action)
419 except self.XenAPI.Failure, exc:
420 LOG.warn(exc)
421 done.send_exception(*sys.exc_info())
422 loop.stop()
423
424 loop.f = _poll_task
425 loop.start(FLAGS.xenapi_task_poll_interval, now=True)
426 return done.wait()
400427
401 def _create_session(self, url):428 def _create_session(self, url):
402 """Stubout point. This can be replaced with a mock session."""429 """Stubout point. This can be replaced with a mock session."""
403 return self.XenAPI.Session(url)430 return self.XenAPI.Session(url)
404431
405 def _poll_task(self, id, task, done):
406 """Poll the given XenAPI task, and fire the given action if we
407 get a result.
408 """
409 try:
410 name = self._session.xenapi.task.get_name_label(task)
411 status = self._session.xenapi.task.get_status(task)
412 if id:
413 action = dict(
414 instance_id=int(id),
415 action=name[0:255], # Ensure action is never > 255
416 error=None)
417 if status == "pending":
418 return
419 elif status == "success":
420 result = self._session.xenapi.task.get_result(task)
421 LOG.info(_("Task [%(name)s] %(task)s status:"
422 " success %(result)s") % locals())
423 done.send(_parse_xmlrpc_value(result))
424 else:
425 error_info = self._session.xenapi.task.get_error_info(task)
426 action["error"] = str(error_info)
427 LOG.warn(_("Task [%(name)s] %(task)s status:"
428 " %(status)s %(error_info)s") % locals())
429 done.send_exception(self.XenAPI.Failure(error_info))
430
431 if id:
432 db.instance_action_create(context.get_admin_context(), action)
433 except self.XenAPI.Failure, exc:
434 LOG.warn(exc)
435 done.send_exception(*sys.exc_info())
436 self._stop_loop()
437
438 def _unwrap_plugin_exceptions(self, func, *args, **kwargs):432 def _unwrap_plugin_exceptions(self, func, *args, **kwargs):
439 """Parse exception details"""433 """Parse exception details"""
440 try:434 try: