Merge lp:~jk0/nova/diagnostics-per-instance into lp:~hudson-openstack/nova/trunk
- diagnostics-per-instance
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Rick Clark |
Approved revision: | 478 |
Merged at revision: | 475 |
Proposed branch: | lp:~jk0/nova/diagnostics-per-instance |
Merge into: | lp:~hudson-openstack/nova/trunk |
Diff against target: |
403 lines (+101/-66) 6 files modified
nova/db/api.py (+5/-0) nova/db/sqlalchemy/api.py (+12/-0) nova/db/sqlalchemy/models.py (+0/-1) nova/tests/virt_unittest.py (+24/-22) nova/virt/xenapi/vmops.py (+22/-18) nova/virt/xenapi_conn.py (+38/-25) |
To merge this branch: | bzr merge lp:~jk0/nova/diagnostics-per-instance |
Related bugs: | |
Related blueprints: |
Run Diagnostics on Instance
(Medium)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eric Day (community) | Needs Information | ||
Rick Clark (community) | Approve | ||
Ed Leafe (community) | Approve | ||
Review via email: mp+44394@code.launchpad.net |
Commit message
Log all XenAPI actions to InstanceActions.
Description of the change
- 475. By Josh Kearney
-
Filter templates and dom0 from list_instances()
- 476. By Josh Kearney
-
Style correction
- 477. By Josh Kearney
-
Merged trunk
- 478. By Josh Kearney
-
Merged trunk
Rick Clark (dendrobates) wrote : | # |
lgtm and tests pass
Eric Day (eday) wrote : | # |
How is the diagnostics data intended to be pulled out and analyzed? Right now it looks like it's only being stored, but there is no API or tools to read it.
Part of my concern with this and the previous branch sticking it into the same core database is that this seems like more of a logging hook, where you could push to some plugin interface, and then write implementations to a queue, another DB with monitoring, etc. It would be great to see more design details around how this data will be accessed and the use cases behind them before we start sticking it all in the DB.
Josh Kearney (jk0) wrote : | # |
> How is the diagnostics data intended to be pulled out and analyzed? Right now
> it looks like it's only being stored, but there is no API or tools to read it.
Currently there is no way to retrieve the data short of making calls directly in SQL (that is my project today -- making this available via the API). My thoughts were to propose these merges in small steps instead of one big chunk, getting lots of good feedback along the way.
> Part of my concern with this and the previous branch sticking it into the same
> core database is that this seems like more of a logging hook, where you could
> push to some plugin interface, and then write implementations to a queue,
> another DB with monitoring, etc. It would be great to see more design details
> around how this data will be accessed and the use cases behind them before we
> start sticking it all in the DB.
I'm just going off of what the blueprint wants for now. I agree that this might not be the best way to store the data, but it's easy enough to change once the distributed data store design is in place. Otherwise I just don't see this getting done in time for Bexar.
Sandy Walsh (sandy-walsh) wrote : | # |
Would like to see tests around _poll_task calling done.send_
If the len(action) > 255, will this bust the operation? Shouldn't just the logging fail, but the action complete?
Perhaps more of general question, but should there be an error code on failures?
Preview Diff
1 | === modified file 'nova/db/api.py' | |||
2 | --- nova/db/api.py 2010-11-03 22:06:00 +0000 | |||
3 | +++ nova/db/api.py 2010-12-22 16:46:27 +0000 | |||
4 | @@ -334,6 +334,11 @@ | |||
5 | 334 | security_group_id) | 334 | security_group_id) |
6 | 335 | 335 | ||
7 | 336 | 336 | ||
8 | 337 | def instance_action_create(context, values): | ||
9 | 338 | """Create an instance action from the values dictionary.""" | ||
10 | 339 | return IMPL.instance_action_create(context, values) | ||
11 | 340 | |||
12 | 341 | |||
13 | 337 | ################### | 342 | ################### |
14 | 338 | 343 | ||
15 | 339 | 344 | ||
16 | 340 | 345 | ||
17 | === modified file 'nova/db/sqlalchemy/api.py' | |||
18 | --- nova/db/sqlalchemy/api.py 2010-12-21 15:25:39 +0000 | |||
19 | +++ nova/db/sqlalchemy/api.py 2010-12-22 16:46:27 +0000 | |||
20 | @@ -749,6 +749,18 @@ | |||
21 | 749 | instance_ref.save(session=session) | 749 | instance_ref.save(session=session) |
22 | 750 | 750 | ||
23 | 751 | 751 | ||
24 | 752 | @require_context | ||
25 | 753 | def instance_action_create(context, values): | ||
26 | 754 | """Create an instance action from the values dictionary.""" | ||
27 | 755 | action_ref = models.InstanceActions() | ||
28 | 756 | action_ref.update(values) | ||
29 | 757 | |||
30 | 758 | session = get_session() | ||
31 | 759 | with session.begin(): | ||
32 | 760 | action_ref.save(session=session) | ||
33 | 761 | return action_ref | ||
34 | 762 | |||
35 | 763 | |||
36 | 752 | ################### | 764 | ################### |
37 | 753 | 765 | ||
38 | 754 | 766 | ||
39 | 755 | 767 | ||
40 | === modified file 'nova/db/sqlalchemy/models.py' | |||
41 | --- nova/db/sqlalchemy/models.py 2010-12-20 20:21:01 +0000 | |||
42 | +++ nova/db/sqlalchemy/models.py 2010-12-22 16:46:27 +0000 | |||
43 | @@ -248,7 +248,6 @@ | |||
44 | 248 | instance_id = Column(Integer, ForeignKey('instances.id')) | 248 | instance_id = Column(Integer, ForeignKey('instances.id')) |
45 | 249 | 249 | ||
46 | 250 | action = Column(String(255)) | 250 | action = Column(String(255)) |
47 | 251 | result = Column(Boolean) | ||
48 | 252 | error = Column(Text) | 251 | error = Column(Text) |
49 | 253 | 252 | ||
50 | 254 | 253 | ||
51 | 255 | 254 | ||
52 | === modified file 'nova/tests/virt_unittest.py' | |||
53 | --- nova/tests/virt_unittest.py 2010-12-17 12:07:43 +0000 | |||
54 | +++ nova/tests/virt_unittest.py 2010-12-22 16:46:27 +0000 | |||
55 | @@ -129,43 +129,45 @@ | |||
56 | 129 | check_list.append(check) | 129 | check_list.append(check) |
57 | 130 | else: | 130 | else: |
58 | 131 | if expect_kernel: | 131 | if expect_kernel: |
61 | 132 | check = (lambda t: t.find('./os/kernel').text.split('/' | 132 | check = (lambda t: t.find('./os/kernel').text.split( |
62 | 133 | )[1], 'kernel') | 133 | '/')[1], 'kernel') |
63 | 134 | else: | 134 | else: |
64 | 135 | check = (lambda t: t.find('./os/kernel'), None) | 135 | check = (lambda t: t.find('./os/kernel'), None) |
65 | 136 | check_list.append(check) | 136 | check_list.append(check) |
66 | 137 | 137 | ||
67 | 138 | if expect_ramdisk: | 138 | if expect_ramdisk: |
70 | 139 | check = (lambda t: t.find('./os/initrd').text.split('/' | 139 | check = (lambda t: t.find('./os/initrd').text.split( |
71 | 140 | )[1], 'ramdisk') | 140 | '/')[1], 'ramdisk') |
72 | 141 | else: | 141 | else: |
73 | 142 | check = (lambda t: t.find('./os/initrd'), None) | 142 | check = (lambda t: t.find('./os/initrd'), None) |
74 | 143 | check_list.append(check) | 143 | check_list.append(check) |
75 | 144 | 144 | ||
76 | 145 | common_checks = [ | 145 | common_checks = [ |
77 | 146 | (lambda t: t.find('.').tag, 'domain'), | 146 | (lambda t: t.find('.').tag, 'domain'), |
88 | 147 | (lambda t: t.find('./devices/interface/filterref/parameter' | 147 | (lambda t: t.find( |
89 | 148 | ).get('name'), 'IP'), | 148 | './devices/interface/filterref/parameter').get('name'), 'IP'), |
90 | 149 | (lambda t: t.find('./devices/interface/filterref/parameter' | 149 | (lambda t: t.find( |
91 | 150 | ).get('value'), '10.11.12.13'), | 150 | './devices/interface/filterref/parameter').get( |
92 | 151 | (lambda t: t.findall('./devices/interface/filterref/parameter' | 151 | 'value'), '10.11.12.13'), |
93 | 152 | )[1].get('name'), 'DHCPSERVER'), | 152 | (lambda t: t.findall( |
94 | 153 | (lambda t: t.findall('./devices/interface/filterref/parameter' | 153 | './devices/interface/filterref/parameter')[1].get( |
95 | 154 | )[1].get('value'), '10.0.0.1'), | 154 | 'name'), 'DHCPSERVER'), |
96 | 155 | (lambda t: t.find('./devices/serial/source').get('path' | 155 | (lambda t: t.findall( |
97 | 156 | ).split('/')[1], 'console.log'), | 156 | './devices/interface/filterref/parameter')[1].get( |
98 | 157 | 'value'), '10.0.0.1'), | ||
99 | 158 | (lambda t: t.find('./devices/serial/source').get( | ||
100 | 159 | 'path').split('/')[1], 'console.log'), | ||
101 | 157 | (lambda t: t.find('./memory').text, '2097152')] | 160 | (lambda t: t.find('./memory').text, '2097152')] |
102 | 158 | 161 | ||
103 | 159 | if rescue: | 162 | if rescue: |
110 | 160 | common_checks += [(lambda t: t.findall('./devices/disk/source' | 163 | common_checks += [ |
111 | 161 | )[0].get('file').split('/')[1], | 164 | (lambda t: t.findall('./devices/disk/source')[0].get( |
112 | 162 | 'rescue-disk'), | 165 | 'file').split('/')[1], 'rescue-disk'), |
113 | 163 | (lambda t: t.findall('./devices/disk/source' | 166 | (lambda t: t.findall('./devices/disk/source')[1].get( |
114 | 164 | )[1].get('file').split('/')[1], | 167 | 'file').split('/')[1], 'disk')] |
109 | 165 | 'disk')] | ||
115 | 166 | else: | 168 | else: |
118 | 167 | common_checks += [(lambda t: t.findall('./devices/disk/source' | 169 | common_checks += [(lambda t: t.findall( |
119 | 168 | )[0].get('file').split('/')[1], | 170 | './devices/disk/source')[0].get('file').split('/')[1], |
120 | 169 | 'disk')] | 171 | 'disk')] |
121 | 170 | 172 | ||
122 | 171 | for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): | 173 | for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): |
123 | 172 | 174 | ||
124 | === modified file 'nova/virt/xenapi/vmops.py' | |||
125 | --- nova/virt/xenapi/vmops.py 2010-12-15 18:19:39 +0000 | |||
126 | +++ nova/virt/xenapi/vmops.py 2010-12-22 16:46:27 +0000 | |||
127 | @@ -44,12 +44,16 @@ | |||
128 | 44 | VMHelper.late_import() | 44 | VMHelper.late_import() |
129 | 45 | 45 | ||
130 | 46 | def list_instances(self): | 46 | def list_instances(self): |
134 | 47 | """ List VM instances """ | 47 | """List VM instances""" |
135 | 48 | return [self._session.get_xenapi().VM.get_name_label(vm) \ | 48 | vms = [] |
136 | 49 | for vm in self._session.get_xenapi().VM.get_all()] | 49 | for vm in self._session.get_xenapi().VM.get_all(): |
137 | 50 | rec = self._session.get_xenapi().VM.get_record(vm) | ||
138 | 51 | if not rec["is_a_template"] and not rec["is_control_domain"]: | ||
139 | 52 | vms.append(rec["name_label"]) | ||
140 | 53 | return vms | ||
141 | 50 | 54 | ||
142 | 51 | def spawn(self, instance): | 55 | def spawn(self, instance): |
144 | 52 | """ Create VM instance """ | 56 | """Create VM instance""" |
145 | 53 | vm = VMHelper.lookup(self._session, instance.name) | 57 | vm = VMHelper.lookup(self._session, instance.name) |
146 | 54 | if vm is not None: | 58 | if vm is not None: |
147 | 55 | raise Exception('Attempted to create non-unique name %s' % | 59 | raise Exception('Attempted to create non-unique name %s' % |
148 | @@ -81,16 +85,16 @@ | |||
149 | 81 | vm_ref) | 85 | vm_ref) |
150 | 82 | 86 | ||
151 | 83 | def reboot(self, instance): | 87 | def reboot(self, instance): |
153 | 84 | """ Reboot VM instance """ | 88 | """Reboot VM instance""" |
154 | 85 | instance_name = instance.name | 89 | instance_name = instance.name |
155 | 86 | vm = VMHelper.lookup(self._session, instance_name) | 90 | vm = VMHelper.lookup(self._session, instance_name) |
156 | 87 | if vm is None: | 91 | if vm is None: |
157 | 88 | raise Exception('instance not present %s' % instance_name) | 92 | raise Exception('instance not present %s' % instance_name) |
158 | 89 | task = self._session.call_xenapi('Async.VM.clean_reboot', vm) | 93 | task = self._session.call_xenapi('Async.VM.clean_reboot', vm) |
160 | 90 | self._session.wait_for_task(task) | 94 | self._session.wait_for_task(instance.id, task) |
161 | 91 | 95 | ||
162 | 92 | def destroy(self, instance): | 96 | def destroy(self, instance): |
164 | 93 | """ Destroy VM instance """ | 97 | """Destroy VM instance""" |
165 | 94 | vm = VMHelper.lookup(self._session, instance.name) | 98 | vm = VMHelper.lookup(self._session, instance.name) |
166 | 95 | if vm is None: | 99 | if vm is None: |
167 | 96 | # Don't complain, just return. This lets us clean up instances | 100 | # Don't complain, just return. This lets us clean up instances |
168 | @@ -101,7 +105,7 @@ | |||
169 | 101 | try: | 105 | try: |
170 | 102 | task = self._session.call_xenapi('Async.VM.hard_shutdown', | 106 | task = self._session.call_xenapi('Async.VM.hard_shutdown', |
171 | 103 | vm) | 107 | vm) |
173 | 104 | self._session.wait_for_task(task) | 108 | self._session.wait_for_task(instance.id, task) |
174 | 105 | except XenAPI.Failure, exc: | 109 | except XenAPI.Failure, exc: |
175 | 106 | logging.warn(exc) | 110 | logging.warn(exc) |
176 | 107 | # Disk clean-up | 111 | # Disk clean-up |
177 | @@ -109,43 +113,43 @@ | |||
178 | 109 | for vdi in vdis: | 113 | for vdi in vdis: |
179 | 110 | try: | 114 | try: |
180 | 111 | task = self._session.call_xenapi('Async.VDI.destroy', vdi) | 115 | task = self._session.call_xenapi('Async.VDI.destroy', vdi) |
182 | 112 | self._session.wait_for_task(task) | 116 | self._session.wait_for_task(instance.id, task) |
183 | 113 | except XenAPI.Failure, exc: | 117 | except XenAPI.Failure, exc: |
184 | 114 | logging.warn(exc) | 118 | logging.warn(exc) |
185 | 115 | try: | 119 | try: |
186 | 116 | task = self._session.call_xenapi('Async.VM.destroy', vm) | 120 | task = self._session.call_xenapi('Async.VM.destroy', vm) |
188 | 117 | self._session.wait_for_task(task) | 121 | self._session.wait_for_task(instance.id, task) |
189 | 118 | except XenAPI.Failure, exc: | 122 | except XenAPI.Failure, exc: |
190 | 119 | logging.warn(exc) | 123 | logging.warn(exc) |
191 | 120 | 124 | ||
193 | 121 | def _wait_with_callback(self, task, callback): | 125 | def _wait_with_callback(self, instance_id, task, callback): |
194 | 122 | ret = None | 126 | ret = None |
195 | 123 | try: | 127 | try: |
197 | 124 | ret = self._session.wait_for_task(task) | 128 | ret = self._session.wait_for_task(instance_id, task) |
198 | 125 | except XenAPI.Failure, exc: | 129 | except XenAPI.Failure, exc: |
199 | 126 | logging.warn(exc) | 130 | logging.warn(exc) |
200 | 127 | callback(ret) | 131 | callback(ret) |
201 | 128 | 132 | ||
202 | 129 | def pause(self, instance, callback): | 133 | def pause(self, instance, callback): |
204 | 130 | """ Pause VM instance """ | 134 | """Pause VM instance""" |
205 | 131 | instance_name = instance.name | 135 | instance_name = instance.name |
206 | 132 | vm = VMHelper.lookup(self._session, instance_name) | 136 | vm = VMHelper.lookup(self._session, instance_name) |
207 | 133 | if vm is None: | 137 | if vm is None: |
208 | 134 | raise Exception('instance not present %s' % instance_name) | 138 | raise Exception('instance not present %s' % instance_name) |
209 | 135 | task = self._session.call_xenapi('Async.VM.pause', vm) | 139 | task = self._session.call_xenapi('Async.VM.pause', vm) |
211 | 136 | self._wait_with_callback(task, callback) | 140 | self._wait_with_callback(instance.id, task, callback) |
212 | 137 | 141 | ||
213 | 138 | def unpause(self, instance, callback): | 142 | def unpause(self, instance, callback): |
215 | 139 | """ Unpause VM instance """ | 143 | """Unpause VM instance""" |
216 | 140 | instance_name = instance.name | 144 | instance_name = instance.name |
217 | 141 | vm = VMHelper.lookup(self._session, instance_name) | 145 | vm = VMHelper.lookup(self._session, instance_name) |
218 | 142 | if vm is None: | 146 | if vm is None: |
219 | 143 | raise Exception('instance not present %s' % instance_name) | 147 | raise Exception('instance not present %s' % instance_name) |
220 | 144 | task = self._session.call_xenapi('Async.VM.unpause', vm) | 148 | task = self._session.call_xenapi('Async.VM.unpause', vm) |
222 | 145 | self._wait_with_callback(task, callback) | 149 | self._wait_with_callback(instance.id, task, callback) |
223 | 146 | 150 | ||
224 | 147 | def get_info(self, instance_id): | 151 | def get_info(self, instance_id): |
226 | 148 | """ Return data about VM instance """ | 152 | """Return data about VM instance""" |
227 | 149 | vm = VMHelper.lookup_blocking(self._session, instance_id) | 153 | vm = VMHelper.lookup_blocking(self._session, instance_id) |
228 | 150 | if vm is None: | 154 | if vm is None: |
229 | 151 | raise Exception('instance not present %s' % instance_id) | 155 | raise Exception('instance not present %s' % instance_id) |
230 | @@ -161,6 +165,6 @@ | |||
231 | 161 | return VMHelper.compile_diagnostics(self._session, rec) | 165 | return VMHelper.compile_diagnostics(self._session, rec) |
232 | 162 | 166 | ||
233 | 163 | def get_console_output(self, instance): | 167 | def get_console_output(self, instance): |
235 | 164 | """ Return snapshot of console """ | 168 | """Return snapshot of console""" |
236 | 165 | # TODO: implement this to fix pylint! | 169 | # TODO: implement this to fix pylint! |
237 | 166 | return 'FAKE CONSOLE OUTPUT of instance' | 170 | return 'FAKE CONSOLE OUTPUT of instance' |
238 | 167 | 171 | ||
239 | === modified file 'nova/virt/xenapi_conn.py' | |||
240 | --- nova/virt/xenapi_conn.py 2010-12-21 15:25:39 +0000 | |||
241 | +++ nova/virt/xenapi_conn.py 2010-12-22 16:46:27 +0000 | |||
242 | @@ -54,6 +54,8 @@ | |||
243 | 54 | from eventlet import event | 54 | from eventlet import event |
244 | 55 | from eventlet import tpool | 55 | from eventlet import tpool |
245 | 56 | 56 | ||
246 | 57 | from nova import context | ||
247 | 58 | from nova import db | ||
248 | 57 | from nova import utils | 59 | from nova import utils |
249 | 58 | from nova import flags | 60 | from nova import flags |
250 | 59 | from nova.virt.xenapi.vmops import VMOps | 61 | from nova.virt.xenapi.vmops import VMOps |
251 | @@ -101,7 +103,7 @@ | |||
252 | 101 | 103 | ||
253 | 102 | 104 | ||
254 | 103 | class XenAPIConnection(object): | 105 | class XenAPIConnection(object): |
256 | 104 | """ A connection to XenServer or Xen Cloud Platform """ | 106 | """A connection to XenServer or Xen Cloud Platform""" |
257 | 105 | 107 | ||
258 | 106 | def __init__(self, url, user, pw): | 108 | def __init__(self, url, user, pw): |
259 | 107 | session = XenAPISession(url, user, pw) | 109 | session = XenAPISession(url, user, pw) |
260 | @@ -109,31 +111,31 @@ | |||
261 | 109 | self._volumeops = VolumeOps(session) | 111 | self._volumeops = VolumeOps(session) |
262 | 110 | 112 | ||
263 | 111 | def list_instances(self): | 113 | def list_instances(self): |
265 | 112 | """ List VM instances """ | 114 | """List VM instances""" |
266 | 113 | return self._vmops.list_instances() | 115 | return self._vmops.list_instances() |
267 | 114 | 116 | ||
268 | 115 | def spawn(self, instance): | 117 | def spawn(self, instance): |
270 | 116 | """ Create VM instance """ | 118 | """Create VM instance""" |
271 | 117 | self._vmops.spawn(instance) | 119 | self._vmops.spawn(instance) |
272 | 118 | 120 | ||
273 | 119 | def reboot(self, instance): | 121 | def reboot(self, instance): |
275 | 120 | """ Reboot VM instance """ | 122 | """Reboot VM instance""" |
276 | 121 | self._vmops.reboot(instance) | 123 | self._vmops.reboot(instance) |
277 | 122 | 124 | ||
278 | 123 | def destroy(self, instance): | 125 | def destroy(self, instance): |
280 | 124 | """ Destroy VM instance """ | 126 | """Destroy VM instance""" |
281 | 125 | self._vmops.destroy(instance) | 127 | self._vmops.destroy(instance) |
282 | 126 | 128 | ||
283 | 127 | def pause(self, instance, callback): | 129 | def pause(self, instance, callback): |
285 | 128 | """ Pause VM instance """ | 130 | """Pause VM instance""" |
286 | 129 | self._vmops.pause(instance, callback) | 131 | self._vmops.pause(instance, callback) |
287 | 130 | 132 | ||
288 | 131 | def unpause(self, instance, callback): | 133 | def unpause(self, instance, callback): |
290 | 132 | """ Unpause paused VM instance """ | 134 | """Unpause paused VM instance""" |
291 | 133 | self._vmops.unpause(instance, callback) | 135 | self._vmops.unpause(instance, callback) |
292 | 134 | 136 | ||
293 | 135 | def get_info(self, instance_id): | 137 | def get_info(self, instance_id): |
295 | 136 | """ Return data about VM instance """ | 138 | """Return data about VM instance""" |
296 | 137 | return self._vmops.get_info(instance_id) | 139 | return self._vmops.get_info(instance_id) |
297 | 138 | 140 | ||
298 | 139 | def get_diagnostics(self, instance_id): | 141 | def get_diagnostics(self, instance_id): |
299 | @@ -141,33 +143,33 @@ | |||
300 | 141 | return self._vmops.get_diagnostics(instance_id) | 143 | return self._vmops.get_diagnostics(instance_id) |
301 | 142 | 144 | ||
302 | 143 | def get_console_output(self, instance): | 145 | def get_console_output(self, instance): |
304 | 144 | """ Return snapshot of console """ | 146 | """Return snapshot of console""" |
305 | 145 | return self._vmops.get_console_output(instance) | 147 | return self._vmops.get_console_output(instance) |
306 | 146 | 148 | ||
307 | 147 | def attach_volume(self, instance_name, device_path, mountpoint): | 149 | def attach_volume(self, instance_name, device_path, mountpoint): |
309 | 148 | """ Attach volume storage to VM instance """ | 150 | """Attach volume storage to VM instance""" |
310 | 149 | return self._volumeops.attach_volume(instance_name, | 151 | return self._volumeops.attach_volume(instance_name, |
311 | 150 | device_path, | 152 | device_path, |
312 | 151 | mountpoint) | 153 | mountpoint) |
313 | 152 | 154 | ||
314 | 153 | def detach_volume(self, instance_name, mountpoint): | 155 | def detach_volume(self, instance_name, mountpoint): |
316 | 154 | """ Detach volume storage to VM instance """ | 156 | """Detach volume storage to VM instance""" |
317 | 155 | return self._volumeops.detach_volume(instance_name, mountpoint) | 157 | return self._volumeops.detach_volume(instance_name, mountpoint) |
318 | 156 | 158 | ||
319 | 157 | 159 | ||
320 | 158 | class XenAPISession(object): | 160 | class XenAPISession(object): |
322 | 159 | """ The session to invoke XenAPI SDK calls """ | 161 | """The session to invoke XenAPI SDK calls""" |
323 | 160 | 162 | ||
324 | 161 | def __init__(self, url, user, pw): | 163 | def __init__(self, url, user, pw): |
325 | 162 | self._session = XenAPI.Session(url) | 164 | self._session = XenAPI.Session(url) |
326 | 163 | self._session.login_with_password(user, pw) | 165 | self._session.login_with_password(user, pw) |
327 | 164 | 166 | ||
328 | 165 | def get_xenapi(self): | 167 | def get_xenapi(self): |
330 | 166 | """ Return the xenapi object """ | 168 | """Return the xenapi object""" |
331 | 167 | return self._session.xenapi | 169 | return self._session.xenapi |
332 | 168 | 170 | ||
333 | 169 | def get_xenapi_host(self): | 171 | def get_xenapi_host(self): |
335 | 170 | """ Return the xenapi host """ | 172 | """Return the xenapi host""" |
336 | 171 | return self._session.xenapi.session.get_this_host(self._session.handle) | 173 | return self._session.xenapi.session.get_this_host(self._session.handle) |
337 | 172 | 174 | ||
338 | 173 | def call_xenapi(self, method, *args): | 175 | def call_xenapi(self, method, *args): |
339 | @@ -183,42 +185,53 @@ | |||
340 | 183 | self._session.xenapi.Async.host.call_plugin, | 185 | self._session.xenapi.Async.host.call_plugin, |
341 | 184 | self.get_xenapi_host(), plugin, fn, args) | 186 | self.get_xenapi_host(), plugin, fn, args) |
342 | 185 | 187 | ||
344 | 186 | def wait_for_task(self, task): | 188 | def wait_for_task(self, instance_id, task): |
345 | 187 | """Return a Deferred that will give the result of the given task. | 189 | """Return a Deferred that will give the result of the given task. |
346 | 188 | The task is polled until it completes.""" | 190 | The task is polled until it completes.""" |
347 | 189 | 191 | ||
348 | 190 | done = event.Event() | 192 | done = event.Event() |
350 | 191 | loop = utils.LoopingCall(self._poll_task, task, done) | 193 | loop = utils.LoopingCall(self._poll_task, instance_id, task, done) |
351 | 192 | loop.start(FLAGS.xenapi_task_poll_interval, now=True) | 194 | loop.start(FLAGS.xenapi_task_poll_interval, now=True) |
352 | 193 | rv = done.wait() | 195 | rv = done.wait() |
353 | 194 | loop.stop() | 196 | loop.stop() |
354 | 195 | return rv | 197 | return rv |
355 | 196 | 198 | ||
357 | 197 | def _poll_task(self, task, done): | 199 | def _poll_task(self, instance_id, task, done): |
358 | 198 | """Poll the given XenAPI task, and fire the given Deferred if we | 200 | """Poll the given XenAPI task, and fire the given Deferred if we |
359 | 199 | get a result.""" | 201 | get a result.""" |
360 | 200 | try: | 202 | try: |
362 | 201 | #logging.debug('Polling task %s...', task) | 203 | name = self._session.xenapi.task.get_name_label(task) |
363 | 202 | status = self._session.xenapi.task.get_status(task) | 204 | status = self._session.xenapi.task.get_status(task) |
365 | 203 | if status == 'pending': | 205 | action = dict( |
366 | 206 | instance_id=int(instance_id), | ||
367 | 207 | action=name, | ||
368 | 208 | error=None) | ||
369 | 209 | if status == "pending": | ||
370 | 204 | return | 210 | return |
372 | 205 | elif status == 'success': | 211 | elif status == "success": |
373 | 206 | result = self._session.xenapi.task.get_result(task) | 212 | result = self._session.xenapi.task.get_result(task) |
375 | 207 | logging.info(_('Task %s status: success. %s'), task, result) | 213 | logging.info(_("Task [%s] %s status: success %s") % ( |
376 | 214 | name, | ||
377 | 215 | task, | ||
378 | 216 | result)) | ||
379 | 208 | done.send(_parse_xmlrpc_value(result)) | 217 | done.send(_parse_xmlrpc_value(result)) |
380 | 209 | else: | 218 | else: |
381 | 210 | error_info = self._session.xenapi.task.get_error_info(task) | 219 | error_info = self._session.xenapi.task.get_error_info(task) |
384 | 211 | logging.warn(_('Task %s status: %s. %s'), task, status, | 220 | action["error"] = str(error_info) |
385 | 212 | error_info) | 221 | logging.warn(_("Task [%s] %s status: %s %s") % ( |
386 | 222 | name, | ||
387 | 223 | task, | ||
388 | 224 | status, | ||
389 | 225 | error_info)) | ||
390 | 213 | done.send_exception(XenAPI.Failure(error_info)) | 226 | done.send_exception(XenAPI.Failure(error_info)) |
392 | 214 | #logging.debug('Polling task %s done.', task) | 227 | db.instance_action_create(context.get_admin_context(), action) |
393 | 215 | except XenAPI.Failure, exc: | 228 | except XenAPI.Failure, exc: |
394 | 216 | logging.warn(exc) | 229 | logging.warn(exc) |
395 | 217 | done.send_exception(*sys.exc_info()) | 230 | done.send_exception(*sys.exc_info()) |
396 | 218 | 231 | ||
397 | 219 | 232 | ||
398 | 220 | def _unwrap_plugin_exceptions(func, *args, **kwargs): | 233 | def _unwrap_plugin_exceptions(func, *args, **kwargs): |
400 | 221 | """ Parse exception details """ | 234 | """Parse exception details""" |
401 | 222 | try: | 235 | try: |
402 | 223 | return func(*args, **kwargs) | 236 | return func(*args, **kwargs) |
403 | 224 | except XenAPI.Failure, exc: | 237 | except XenAPI.Failure, exc: |
Looks good.