Merge lp:~tr3buchet/nova/xs_suspend into lp:~hudson-openstack/nova/trunk

Proposed by Trey Morris
Status: Merged
Approved by: Jay Pipes
Approved revision: 479
Merged at revision: 497
Proposed branch: lp:~tr3buchet/nova/xs_suspend
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 360 lines (+175/-9)
12 files modified
nova/api/openstack/__init__.py (+2/-0)
nova/api/openstack/servers.py (+26/-3)
nova/compute/api.py (+18/-0)
nova/compute/manager.py (+33/-0)
nova/compute/power_state.py (+3/-1)
nova/tests/api/openstack/test_servers.py (+30/-2)
nova/tests/test_compute.py (+10/-2)
nova/virt/fake.py (+12/-0)
nova/virt/libvirt_conn.py (+8/-0)
nova/virt/xenapi/vm_utils.py (+5/-1)
nova/virt/xenapi/vmops.py (+20/-0)
nova/virt/xenapi_conn.py (+8/-0)
To merge this branch: bzr merge lp:~tr3buchet/nova/xs_suspend
Reviewer Review Type Date Requested Status
Jay Pipes (community) Approve
Matt Dietz (community) Approve
Review via email: mp+44550@code.launchpad.net

Description of the change

I've added suspend along with a few changes to power state as well. I can't imagine suspend will be controversial but I've added a new power state for "suspended" to nova.compute.power_states which libvirt doesn't use and updated the xenapi power mapping to use it for suspended state. I also updated the mappings in nova.api.openstack.servers to map PAUSED to "error" and SUSPENDED to "suspended". Thoughts there are that we don't currently (openstack API v1.0) use pause, so if somehow an instance were to be paused an error occurred somewhere, or someone did something in error. Either way asking the xenserver host for the status would show "paused". Support for more power states needs to be added to the next version of the openstack API.

merge notes:
merge with trunk went well locally
unittests passed locally
testing of suspend succeeded locally

Notes for testing:
Suspending an instance requires PV drivers.
If using the cloudservers API, suspend and resume are not implemented. I tested by modifying in place nova.api.openstack.servers after pulling my branch. I made pause() and unpause() immediately return self.suspend() and self.resume(). Afterwards cloudserver pause (id) and cloudserver unpause (id) will perform the suspend/resume, and you can watch the compute worker log for info on what is happening.

SAMPLE LOG OUTPUT FOR PAUSE/UNPAUSE +++++++++++++++++++++
DEBUG:root:instance 1178831109: pausing
INFO:root:Task OpaqueRef:ba3b1595-0d70-574a-aaca-27442d04be87 status: success.
INFO:root:(VM_UTILS) xenserver vm state -> |Paused|
INFO:root:(VM_UTILS) xenapi power_state -> |3|
DEBUG:root:instance 1178831109: unpausing
INFO:root:Task OpaqueRef:2d9cb792-0ce5-be23-6c50-3dc52cf1535c status: success.
INFO:root:(VM_UTILS) xenserver vm state -> |Running|
INFO:root:(VM_UTILS) xenapi power_state -> |1|

SAMPLE LOG OUTPUT FOR SUSPEND/RESUME +++++++++++++++++++++
DEBUG:root:instance 1178831109: suspending
INFO:root:Task OpaqueRef:bc5d51da-922f-c9fb-f07c-aaa027fde601 status: success.
INFO:root:(VM_UTILS) xenserver vm state -> |Suspended|
INFO:root:(VM_UTILS) xenapi power_state -> |7|
DEBUG:root:instance 1178831109: resuming
INFO:root:Task OpaqueRef:4e5a3af8-baed-c144-30ea-63637390f6c8 status: success.
INFO:root:(VM_UTILS) xenserver vm state -> |Running|
INFO:root:(VM_UTILS) xenapi power_state -> |1|

To post a comment you must log in.
Revision history for this message
Armando Migliaccio (armando-migliaccio) wrote :

It is true that xenapi-unittest is a throw's stone away from being in trunk, but I would encourage start writing unit tests for xenapi along with this branch. During my last merges a number of changes broke the ones written in lp:~armando-migliaccio/nova/xenapi-unittests and that helped me a lot to figure out how to fix things.

Plus, it would be useful very useful for us getting an early feedback on how the fake xenapi works in practice for everyone.
Thanks,
Armando

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

Hi!

Good stuff, Trey! And thanks for the answers to my questions on IRC :)

One tiny thing to address:

Please mark all strings that go to log files with _() so they may be picked up by gettext and the message building facilities.

So, things like:

95 + logging.debug('instance %s: suspending', instance_ref['internal_id'])

should be:

95 + logging.debug(_('instance %s: suspending'), instance_ref['internal_id'])

the little _()'s mark the string 'instance %s: suspending' to be translated.

Cheers!
jay

review: Needs Fixing
Revision history for this message
Trey Morris (tr3buchet) wrote :

will do!

Revision history for this message
Matt Dietz (cerberus) wrote :

I disagree with the assessment that we have implementation specific unit tests for generalized functionality. Something like suspend should, at least in theory, operate in the same abstracted way across all supporting hypervisors. I would argue that his tests in this regard are sufficient. For XenServer specific functionality, such as xenstore interaction, the maybe we want targeted tests.

Otherwise, it looks good to me, minus Jay's internationalization fixes.

review: Approve
Revision history for this message
Trey Morris (tr3buchet) wrote :

alrighty, fixed. Sorry for the delay, I bit off a huge chunk of messages to correct with _(). That coupled with some large changes to trunk left me a giant merge, so I backed off some.

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

Hi again!

Good stuff! Missed 2 strings, though :)

323 + raise Exception("suspend: instance not present %s" % instance_name)
and
332 + raise Exception("resume: instance not present %s" % instance_name)

fix those up and approve from me! :)

Revision history for this message
Armando Migliaccio (armando-migliaccio) wrote :

Matthew,

I am not suggesting to have implementation specific unit tests per se. I am suggesting to add tests where it makes sense. I saw features being added to xenapi, so I sounded sensible to me suggesting a number of tests to verify that part of the code.

> I disagree with the assessment that we have implementation specific unit tests
> for generalized functionality. Something like suspend should, at least in
> theory, operate in the same abstracted way across all supporting hypervisors.
> I would argue that his tests in this regard are sufficient. For XenServer
> specific functionality, such as xenstore interaction, the maybe we want
> targeted tests.
>
> Otherwise, it looks good to me, minus Jay's internationalization fixes.

Revision history for this message
Trey Morris (tr3buchet) wrote :

Jay Pipes: fixed!

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

w00tness.

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

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

nova/compute/manager.py:305:80: E501 line too long (80 characters)
        logging.debug(_('instance %s: suspending'), instance_ref['internal_id'])
                                                                               ^
    Limit all lines to a maximum of 79 characters.

    There are still many devices around that are limited to 80 character
    lines; plus, limiting windows to 80 characters makes it possible to have
    several windows side-by-side. The default wrapping on such devices looks
    ugly. Therefore, please limit all lines to a maximum of 79 characters.
    For flowing long blocks of text (docstrings or comments), limiting the
    length to 72 characters is recommended.

lp:~tr3buchet/nova/xs_suspend updated
479. By Trey Morris

fixed a line length

Revision history for this message
Trey Morris (tr3buchet) wrote :

fixed... Can't believe I missed that.

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

:)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/api/openstack/__init__.py'
2--- nova/api/openstack/__init__.py 2010-12-23 18:06:00 +0000
3+++ nova/api/openstack/__init__.py 2010-12-28 05:16:45 +0000
4@@ -93,6 +93,8 @@
5 logging.debug("Including admin operations in API.")
6 server_members['pause'] = 'POST'
7 server_members['unpause'] = 'POST'
8+ server_members['suspend'] = 'POST'
9+ server_members['resume'] = 'POST'
10
11 mapper.resource("server", "servers", controller=servers.Controller(),
12 collection={'detail': 'GET'},
13
14=== modified file 'nova/api/openstack/servers.py'
15--- nova/api/openstack/servers.py 2010-12-23 18:06:00 +0000
16+++ nova/api/openstack/servers.py 2010-12-28 05:16:45 +0000
17@@ -46,7 +46,8 @@
18 power_state.NOSTATE: 'build',
19 power_state.RUNNING: 'active',
20 power_state.BLOCKED: 'active',
21- power_state.PAUSED: 'suspended',
22+ power_state.SUSPENDED: 'suspended',
23+ power_state.PAUSED: 'error',
24 power_state.SHUTDOWN: 'active',
25 power_state.SHUTOFF: 'active',
26 power_state.CRASHED: 'error'}
27@@ -182,7 +183,7 @@
28 self.compute_api.pause(ctxt, id)
29 except:
30 readable = traceback.format_exc()
31- logging.error("Compute.api::pause %s", readable)
32+ logging.error(_("Compute.api::pause %s"), readable)
33 return faults.Fault(exc.HTTPUnprocessableEntity())
34 return exc.HTTPAccepted()
35
36@@ -193,6 +194,28 @@
37 self.compute_api.unpause(ctxt, id)
38 except:
39 readable = traceback.format_exc()
40- logging.error("Compute.api::unpause %s", readable)
41+ logging.error(_("Compute.api::unpause %s"), readable)
42+ return faults.Fault(exc.HTTPUnprocessableEntity())
43+ return exc.HTTPAccepted()
44+
45+ def suspend(self, req, id):
46+ """permit admins to suspend the server"""
47+ context = req.environ['nova.context']
48+ try:
49+ self.compute_api.suspend(context, id)
50+ except:
51+ readable = traceback.format_exc()
52+ logging.error(_("compute.api::suspend %s"), readable)
53+ return faults.Fault(exc.HTTPUnprocessableEntity())
54+ return exc.HTTPAccepted()
55+
56+ def resume(self, req, id):
57+ """permit admins to resume the server from suspend"""
58+ context = req.environ['nova.context']
59+ try:
60+ self.compute_api.resume(context, id)
61+ except:
62+ readable = traceback.format_exc()
63+ logging.error(_("compute.api::resume %s"), readable)
64 return faults.Fault(exc.HTTPUnprocessableEntity())
65 return exc.HTTPAccepted()
66
67=== modified file 'nova/compute/api.py'
68--- nova/compute/api.py 2010-12-23 21:41:54 +0000
69+++ nova/compute/api.py 2010-12-28 05:16:45 +0000
70@@ -284,6 +284,24 @@
71 {"method": "unpause_instance",
72 "args": {"instance_id": instance['id']}})
73
74+ def suspend(self, context, instance_id):
75+ """suspend the instance with instance_id"""
76+ instance = self.db.instance_get_by_internal_id(context, instance_id)
77+ host = instance['host']
78+ rpc.cast(context,
79+ self.db.queue_get_for(context, FLAGS.compute_topic, host),
80+ {"method": "suspend_instance",
81+ "args": {"instance_id": instance['id']}})
82+
83+ def resume(self, context, instance_id):
84+ """resume the instance with instance_id"""
85+ instance = self.db.instance_get_by_internal_id(context, instance_id)
86+ host = instance['host']
87+ rpc.cast(context,
88+ self.db.queue_get_for(context, FLAGS.compute_topic, host),
89+ {"method": "resume_instance",
90+ "args": {"instance_id": instance['id']}})
91+
92 def rescue(self, context, instance_id):
93 """Rescue the given instance."""
94 instance = self.db.instance_get_by_internal_id(context, instance_id)
95
96=== modified file 'nova/compute/manager.py'
97--- nova/compute/manager.py 2010-12-22 22:54:13 +0000
98+++ nova/compute/manager.py 2010-12-28 05:16:45 +0000
99@@ -297,6 +297,39 @@
100 result))
101
102 @exception.wrap_exception
103+ def suspend_instance(self, context, instance_id):
104+ """suspend the instance with instance_id"""
105+ context = context.elevated()
106+ instance_ref = self.db.instance_get(context, instance_id)
107+
108+ logging.debug(_('instance %s: suspending'),
109+ instance_ref['internal_id'])
110+ self.db.instance_set_state(context, instance_id,
111+ power_state.NOSTATE,
112+ 'suspending')
113+ self.driver.suspend(instance_ref,
114+ lambda result: self._update_state_callback(self,
115+ context,
116+ instance_id,
117+ result))
118+
119+ @exception.wrap_exception
120+ def resume_instance(self, context, instance_id):
121+ """resume the suspended instance with instance_id"""
122+ context = context.elevated()
123+ instance_ref = self.db.instance_get(context, instance_id)
124+
125+ logging.debug(_('instance %s: resuming'), instance_ref['internal_id'])
126+ self.db.instance_set_state(context, instance_id,
127+ power_state.NOSTATE,
128+ 'resuming')
129+ self.driver.resume(instance_ref,
130+ lambda result: self._update_state_callback(self,
131+ context,
132+ instance_id,
133+ result))
134+
135+ @exception.wrap_exception
136 def get_console_output(self, context, instance_id):
137 """Send the console output for an instance."""
138 context = context.elevated()
139
140=== modified file 'nova/compute/power_state.py'
141--- nova/compute/power_state.py 2010-10-22 00:15:21 +0000
142+++ nova/compute/power_state.py 2010-12-28 05:16:45 +0000
143@@ -26,6 +26,7 @@
144 SHUTDOWN = 0x04
145 SHUTOFF = 0x05
146 CRASHED = 0x06
147+SUSPENDED = 0x07
148
149
150 def name(code):
151@@ -36,5 +37,6 @@
152 PAUSED: 'paused',
153 SHUTDOWN: 'shutdown',
154 SHUTOFF: 'shutdown',
155- CRASHED: 'crashed'}
156+ CRASHED: 'crashed',
157+ SUSPENDED: 'suspended'}
158 return d[code]
159
160=== modified file 'nova/tests/api/openstack/test_servers.py'
161--- nova/tests/api/openstack/test_servers.py 2010-12-23 18:06:00 +0000
162+++ nova/tests/api/openstack/test_servers.py 2010-12-28 05:16:45 +0000
163@@ -88,9 +88,13 @@
164 self.stubs.Set(nova.db.api, 'instance_get_floating_address',
165 instance_address)
166 self.stubs.Set(nova.compute.api.ComputeAPI, 'pause',
167- fake_compute_api)
168+ fake_compute_api)
169 self.stubs.Set(nova.compute.api.ComputeAPI, 'unpause',
170- fake_compute_api)
171+ fake_compute_api)
172+ self.stubs.Set(nova.compute.api.ComputeAPI, 'suspend',
173+ fake_compute_api)
174+ self.stubs.Set(nova.compute.api.ComputeAPI, 'resume',
175+ fake_compute_api)
176 self.allow_admin = FLAGS.allow_admin_api
177
178 def tearDown(self):
179@@ -246,6 +250,30 @@
180 res = req.get_response(nova.api.API('os'))
181 self.assertEqual(res.status_int, 202)
182
183+ def test_server_suspend(self):
184+ FLAGS.allow_admin_api = True
185+ body = dict(server=dict(
186+ name='server_test', imageId=2, flavorId=2, metadata={},
187+ personality={}))
188+ req = webob.Request.blank('/v1.0/servers/1/suspend')
189+ req.method = 'POST'
190+ req.content_type = 'application/json'
191+ req.body = json.dumps(body)
192+ res = req.get_response(nova.api.API('os'))
193+ self.assertEqual(res.status_int, 202)
194+
195+ def test_server_resume(self):
196+ FLAGS.allow_admin_api = True
197+ body = dict(server=dict(
198+ name='server_test', imageId=2, flavorId=2, metadata={},
199+ personality={}))
200+ req = webob.Request.blank('/v1.0/servers/1/resume')
201+ req.method = 'POST'
202+ req.content_type = 'application/json'
203+ req.body = json.dumps(body)
204+ res = req.get_response(nova.api.API('os'))
205+ self.assertEqual(res.status_int, 202)
206+
207 def test_server_reboot(self):
208 body = dict(server=dict(
209 name='server_test', imageId=2, flavorId=2, metadata={},
210
211=== modified file 'nova/tests/test_compute.py'
212--- nova/tests/test_compute.py 2010-12-23 18:31:46 +0000
213+++ nova/tests/test_compute.py 2010-12-28 05:16:45 +0000
214@@ -101,13 +101,13 @@
215 self.compute.run_instance(self.context, instance_id)
216
217 instances = db.instance_get_all(context.get_admin_context())
218- logging.info("Running instances: %s", instances)
219+ logging.info(_("Running instances: %s"), instances)
220 self.assertEqual(len(instances), 1)
221
222 self.compute.terminate_instance(self.context, instance_id)
223
224 instances = db.instance_get_all(context.get_admin_context())
225- logging.info("After terminating instances: %s", instances)
226+ logging.info(_("After terminating instances: %s"), instances)
227 self.assertEqual(len(instances), 0)
228
229 def test_run_terminate_timestamps(self):
230@@ -136,6 +136,14 @@
231 self.compute.unpause_instance(self.context, instance_id)
232 self.compute.terminate_instance(self.context, instance_id)
233
234+ def test_suspend(self):
235+ """ensure instance can be suspended"""
236+ instance_id = self._create_instance()
237+ self.compute.run_instance(self.context, instance_id)
238+ self.compute.suspend_instance(self.context, instance_id)
239+ self.compute.resume_instance(self.context, instance_id)
240+ self.compute.terminate_instance(self.context, instance_id)
241+
242 def test_reboot(self):
243 """Ensure instance can be rebooted"""
244 instance_id = self._create_instance()
245
246=== modified file 'nova/virt/fake.py'
247--- nova/virt/fake.py 2010-12-22 20:59:53 +0000
248+++ nova/virt/fake.py 2010-12-28 05:16:45 +0000
249@@ -148,6 +148,18 @@
250 """
251 pass
252
253+ def suspend(self, instance, callback):
254+ """
255+ suspend the specified instance
256+ """
257+ pass
258+
259+ def resume(self, instance, callback):
260+ """
261+ resume the specified instance
262+ """
263+ pass
264+
265 def destroy(self, instance):
266 """
267 Destroy (shutdown and delete) the specified instance.
268
269=== modified file 'nova/virt/libvirt_conn.py'
270--- nova/virt/libvirt_conn.py 2010-12-27 10:04:25 +0000
271+++ nova/virt/libvirt_conn.py 2010-12-28 05:16:45 +0000
272@@ -280,6 +280,14 @@
273 raise exception.APIError("unpause not supported for libvirt.")
274
275 @exception.wrap_exception
276+ def suspend(self, instance, callback):
277+ raise exception.APIError("suspend not supported for libvirt")
278+
279+ @exception.wrap_exception
280+ def resume(self, instance, callback):
281+ raise exception.APIError("resume not supported for libvirt")
282+
283+ @exception.wrap_exception
284 def rescue(self, instance):
285 self.destroy(instance, False)
286
287
288=== modified file 'nova/virt/xenapi/vm_utils.py'
289--- nova/virt/xenapi/vm_utils.py 2010-12-23 21:41:54 +0000
290+++ nova/virt/xenapi/vm_utils.py 2010-12-28 05:16:45 +0000
291@@ -39,7 +39,7 @@
292 'Halted': power_state.SHUTDOWN,
293 'Running': power_state.RUNNING,
294 'Paused': power_state.PAUSED,
295- 'Suspended': power_state.SHUTDOWN, # FIXME
296+ 'Suspended': power_state.SUSPENDED,
297 'Crashed': power_state.CRASHED}
298
299
300@@ -283,6 +283,10 @@
301 @classmethod
302 def compile_info(cls, record):
303 """Fill record with VM status information"""
304+ logging.info(_("(VM_UTILS) xenserver vm state -> |%s|"),
305+ record['power_state'])
306+ logging.info(_("(VM_UTILS) xenapi power_state -> |%s|"),
307+ XENAPI_POWER_STATE[record['power_state']])
308 return {'state': XENAPI_POWER_STATE[record['power_state']],
309 'max_mem': long(record['memory_static_max']) >> 10,
310 'mem': long(record['memory_dynamic_max']) >> 10,
311
312=== modified file 'nova/virt/xenapi/vmops.py'
313--- nova/virt/xenapi/vmops.py 2010-12-23 21:41:54 +0000
314+++ nova/virt/xenapi/vmops.py 2010-12-28 05:16:45 +0000
315@@ -188,6 +188,26 @@
316 task = self._session.call_xenapi('Async.VM.unpause', vm)
317 self._wait_with_callback(instance.id, task, callback)
318
319+ def suspend(self, instance, callback):
320+ """suspend the specified instance"""
321+ instance_name = instance.name
322+ vm = VMHelper.lookup(self._session, instance_name)
323+ if vm is None:
324+ raise Exception(_("suspend: instance not present %s") %
325+ instance_name)
326+ task = self._session.call_xenapi('Async.VM.suspend', vm)
327+ self._wait_with_callback(task, callback)
328+
329+ def resume(self, instance, callback):
330+ """resume the specified instance"""
331+ instance_name = instance.name
332+ vm = VMHelper.lookup(self._session, instance_name)
333+ if vm is None:
334+ raise Exception(_("resume: instance not present %s") %
335+ instance_name)
336+ task = self._session.call_xenapi('Async.VM.resume', vm, False, True)
337+ self._wait_with_callback(task, callback)
338+
339 def get_info(self, instance_id):
340 """Return data about VM instance"""
341 vm = VMHelper.lookup(self._session, instance_id)
342
343=== modified file 'nova/virt/xenapi_conn.py'
344--- nova/virt/xenapi_conn.py 2010-12-23 03:35:41 +0000
345+++ nova/virt/xenapi_conn.py 2010-12-28 05:16:45 +0000
346@@ -147,6 +147,14 @@
347 """Unpause paused VM instance"""
348 self._vmops.unpause(instance, callback)
349
350+ def suspend(self, instance, callback):
351+ """suspend the specified instance"""
352+ self._vmops.suspend(instance, callback)
353+
354+ def resume(self, instance, callback):
355+ """resume the specified instance"""
356+ self._vmops.resume(instance, callback)
357+
358 def get_info(self, instance_id):
359 """Return data about VM instance"""
360 return self._vmops.get_info(instance_id)