1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright (c) 2010 Citrix Systems, Inc.
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6
# not use this file except in compliance with the License. You may obtain
7
# a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
# License for the specific language governing permissions and limitations
18
A connection to XenServer or Xen Cloud Platform.
20
The concurrency model for this class is as follows:
22
All XenAPI calls are on a thread (using t.i.t.deferToThread, via the decorator
23
deferredToThread). They are remote calls, and so may hang for the usual
24
reasons. They should not be allowed to block the reactor thread.
26
All long-running XenAPI calls (VM.start, VM.reboot, etc) are called async
27
(using XenAPI.VM.async_start etc). These return a task, which can then be
28
polled for completion. Polling is handled using reactor.callLater.
30
This combination of techniques means that we don't block the reactor thread at
31
all, and at the same time we don't hold lots of threads waiting for
32
long-running operations.
34
FIXME: get_info currently doesn't conform to these rules, and will block the
35
reactor thread if the VM.get_by_name_label or VM.get_record calls block.
39
:xenapi_connection_url: URL for connection to XenServer/Xen Cloud Platform.
40
:xenapi_connection_username: Username for connection to XenServer/Xen Cloud
41
Platform (default: root).
42
:xenapi_connection_password: Password for connection to XenServer/Xen Cloud
44
:xenapi_task_poll_interval: The interval (seconds) used for polling of
45
remote tasks (Async.VM.start, etc)
53
from twisted.internet import defer
54
from twisted.internet import reactor
56
from nova import utils
57
from nova import flags
58
from nova.virt.xenapi.vmops import VMOps
59
from nova.virt.xenapi.volumeops import VolumeOps
62
flags.DEFINE_string('xenapi_connection_url',
64
'URL for connection to XenServer/Xen Cloud Platform.'
65
' Required if connection_type=xenapi.')
66
flags.DEFINE_string('xenapi_connection_username',
68
'Username for connection to XenServer/Xen Cloud Platform.'
69
' Used only if connection_type=xenapi.')
70
flags.DEFINE_string('xenapi_connection_password',
72
'Password for connection to XenServer/Xen Cloud Platform.'
73
' Used only if connection_type=xenapi.')
74
flags.DEFINE_float('xenapi_task_poll_interval',
76
'The interval used for polling of remote tasks '
77
'(Async.VM.start, etc). Used only if '
78
'connection_type=xenapi.')
83
def get_connection(_):
84
"""Note that XenAPI doesn't have a read-only connection mode, so
85
the read_only parameter is ignored."""
86
# This is loaded late so that there's no need to install this
87
# library when not using XenAPI.
90
XenAPI = __import__('XenAPI')
91
url = FLAGS.xenapi_connection_url
92
username = FLAGS.xenapi_connection_username
93
password = FLAGS.xenapi_connection_password
94
if not url or password is None:
95
raise Exception('Must specify xenapi_connection_url, '
96
'xenapi_connection_username (optionally), and '
97
'xenapi_connection_password to use '
98
'connection_type=xenapi')
99
return XenAPIConnection(url, username, password)
102
class XenAPIConnection(object):
103
""" A connection to XenServer or Xen Cloud Platform """
104
def __init__(self, url, user, pw):
105
session = XenAPISession(url, user, pw)
106
self._vmops = VMOps(session)
107
self._volumeops = VolumeOps(session)
109
def list_instances(self):
110
""" List VM instances """
111
return self._vmops.list_instances()
113
def spawn(self, instance):
114
""" Create VM instance """
115
self._vmops.spawn(instance)
117
def reboot(self, instance):
118
""" Reboot VM instance """
119
self._vmops.reboot(instance)
121
def destroy(self, instance):
122
""" Destroy VM instance """
123
self._vmops.destroy(instance)
125
def get_info(self, instance_id):
126
""" Return data about VM instance """
127
return self._vmops.get_info(instance_id)
129
def get_diagnostics(self, instance_id):
130
"""Return data about VM diagnostics"""
131
return self._vmops.get_diagnostics(instance_id)
133
def get_console_output(self, instance):
134
""" Return snapshot of console """
135
return self._vmops.get_console_output(instance)
137
def attach_volume(self, instance_name, device_path, mountpoint):
138
""" Attach volume storage to VM instance """
139
return self._volumeops.attach_volume(instance_name,
143
def detach_volume(self, instance_name, mountpoint):
144
""" Detach volume storage to VM instance """
145
return self._volumeops.detach_volume(instance_name, mountpoint)
148
class XenAPISession(object):
149
""" The session to invoke XenAPI SDK calls """
150
def __init__(self, url, user, pw):
151
self._session = XenAPI.Session(url)
152
self._session.login_with_password(user, pw)
154
def get_xenapi(self):
155
""" Return the xenapi object """
156
return self._session.xenapi
158
def get_xenapi_host(self):
159
""" Return the xenapi host """
160
return self._session.xenapi.session.get_this_host(self._session.handle)
162
@utils.deferredToThread
163
def call_xenapi(self, method, *args):
164
"""Call the specified XenAPI method on a background thread. Returns
165
a Deferred for the result."""
166
f = self._session.xenapi
167
for m in method.split('.'):
171
@utils.deferredToThread
172
def async_call_plugin(self, plugin, fn, args):
173
"""Call Async.host.call_plugin on a background thread. Returns a
174
Deferred with the task reference."""
175
return _unwrap_plugin_exceptions(
176
self._session.xenapi.Async.host.call_plugin,
177
self.get_xenapi_host(), plugin, fn, args)
179
def wait_for_task(self, task):
180
"""Return a Deferred that will give the result of the given task.
181
The task is polled until it completes."""
183
reactor.callLater(0, self._poll_task, task, d)
186
@utils.deferredToThread
187
def _poll_task(self, task, deferred):
188
"""Poll the given XenAPI task, and fire the given Deferred if we
191
#logging.debug('Polling task %s...', task)
192
status = self._session.xenapi.task.get_status(task)
193
if status == 'pending':
194
reactor.callLater(FLAGS.xenapi_task_poll_interval,
195
self._poll_task, task, deferred)
196
elif status == 'success':
197
result = self._session.xenapi.task.get_result(task)
198
logging.info('Task %s status: success. %s', task, result)
199
deferred.callback(_parse_xmlrpc_value(result))
201
error_info = self._session.xenapi.task.get_error_info(task)
202
logging.warn('Task %s status: %s. %s', task, status,
204
deferred.errback(XenAPI.Failure(error_info))
205
#logging.debug('Polling task %s done.', task)
206
except XenAPI.Failure, exc:
208
deferred.errback(exc)
211
def _unwrap_plugin_exceptions(func, *args, **kwargs):
212
""" Parse exception details """
214
return func(*args, **kwargs)
215
except XenAPI.Failure, exc:
216
logging.debug("Got exception: %s", exc)
217
if (len(exc.details) == 4 and
218
exc.details[0] == 'XENAPI_PLUGIN_EXCEPTION' and
219
exc.details[2] == 'Failure'):
222
params = eval(exc.details[3])
225
raise XenAPI.Failure(params)
228
except xmlrpclib.ProtocolError, exc:
229
logging.debug("Got exception: %s", exc)
233
def _parse_xmlrpc_value(val):
234
"""Parse the given value as if it were an XML-RPC value. This is
235
sometimes used as the format for the task.result field."""
239
'<?xml version="1.0"?><methodResponse><params><param>' +
241
'</param></params></methodResponse>')