~ubuntu-branches/ubuntu/quantal/nova/quantal-proposed

« back to all changes in this revision

Viewing changes to nova/virt/xenapi_conn.py

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2010-12-13 10:17:01 UTC
  • mto: This revision was merged to the branch mainline in revision 8.
  • Revision ID: james.westby@ubuntu.com-20101213101701-txhhqbzsxw4avnxv
Tags: upstream-2011.1~bzr456
ImportĀ upstreamĀ versionĀ 2011.1~bzr456

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright (c) 2010 Citrix Systems, Inc.
 
4
#
 
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
 
8
#
 
9
#         http://www.apache.org/licenses/LICENSE-2.0
 
10
#
 
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
 
15
#    under the License.
 
16
 
 
17
"""
 
18
A connection to XenServer or Xen Cloud Platform.
 
19
 
 
20
The concurrency model for this class is as follows:
 
21
 
 
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.
 
25
 
 
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.
 
29
 
 
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.
 
33
 
 
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.
 
36
 
 
37
**Related Flags**
 
38
 
 
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
 
43
                              Platform.
 
44
:xenapi_task_poll_interval:  The interval (seconds) used for polling of
 
45
                             remote tasks (Async.VM.start, etc)
 
46
                             (default: 0.5).
 
47
 
 
48
"""
 
49
 
 
50
import logging
 
51
import xmlrpclib
 
52
 
 
53
from twisted.internet import defer
 
54
from twisted.internet import reactor
 
55
 
 
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
 
60
 
 
61
FLAGS = flags.FLAGS
 
62
flags.DEFINE_string('xenapi_connection_url',
 
63
                    None,
 
64
                    'URL for connection to XenServer/Xen Cloud Platform.'
 
65
                    ' Required if connection_type=xenapi.')
 
66
flags.DEFINE_string('xenapi_connection_username',
 
67
                    'root',
 
68
                    'Username for connection to XenServer/Xen Cloud Platform.'
 
69
                    ' Used only if connection_type=xenapi.')
 
70
flags.DEFINE_string('xenapi_connection_password',
 
71
                    None,
 
72
                    'Password for connection to XenServer/Xen Cloud Platform.'
 
73
                    ' Used only if connection_type=xenapi.')
 
74
flags.DEFINE_float('xenapi_task_poll_interval',
 
75
                   0.5,
 
76
                   'The interval used for polling of remote tasks '
 
77
                   '(Async.VM.start, etc).  Used only if '
 
78
                   'connection_type=xenapi.')
 
79
 
 
80
XenAPI = None
 
81
 
 
82
 
 
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.
 
88
    global XenAPI
 
89
    if XenAPI is None:
 
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)
 
100
 
 
101
 
 
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)
 
108
 
 
109
    def list_instances(self):
 
110
        """ List VM instances """
 
111
        return self._vmops.list_instances()
 
112
 
 
113
    def spawn(self, instance):
 
114
        """ Create VM instance """
 
115
        self._vmops.spawn(instance)
 
116
 
 
117
    def reboot(self, instance):
 
118
        """ Reboot VM instance """
 
119
        self._vmops.reboot(instance)
 
120
 
 
121
    def destroy(self, instance):
 
122
        """ Destroy VM instance """
 
123
        self._vmops.destroy(instance)
 
124
 
 
125
    def get_info(self, instance_id):
 
126
        """ Return data about VM instance """
 
127
        return self._vmops.get_info(instance_id)
 
128
 
 
129
    def get_diagnostics(self, instance_id):
 
130
        """Return data about VM diagnostics"""
 
131
        return self._vmops.get_diagnostics(instance_id)
 
132
 
 
133
    def get_console_output(self, instance):
 
134
        """ Return snapshot of console """
 
135
        return self._vmops.get_console_output(instance)
 
136
 
 
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,
 
140
                                               device_path,
 
141
                                               mountpoint)
 
142
 
 
143
    def detach_volume(self, instance_name, mountpoint):
 
144
        """ Detach volume storage to VM instance """
 
145
        return self._volumeops.detach_volume(instance_name, mountpoint)
 
146
 
 
147
 
 
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)
 
153
 
 
154
    def get_xenapi(self):
 
155
        """ Return the xenapi object """
 
156
        return self._session.xenapi
 
157
 
 
158
    def get_xenapi_host(self):
 
159
        """ Return the xenapi host """
 
160
        return self._session.xenapi.session.get_this_host(self._session.handle)
 
161
 
 
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('.'):
 
168
            f = f.__getattr__(m)
 
169
        return f(*args)
 
170
 
 
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)
 
178
 
 
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."""
 
182
        d = defer.Deferred()
 
183
        reactor.callLater(0, self._poll_task, task, d)
 
184
        return d
 
185
 
 
186
    @utils.deferredToThread
 
187
    def _poll_task(self, task, deferred):
 
188
        """Poll the given XenAPI task, and fire the given Deferred if we
 
189
        get a result."""
 
190
        try:
 
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))
 
200
            else:
 
201
                error_info = self._session.xenapi.task.get_error_info(task)
 
202
                logging.warn('Task %s status: %s.  %s', task, status,
 
203
                             error_info)
 
204
                deferred.errback(XenAPI.Failure(error_info))
 
205
            #logging.debug('Polling task %s done.', task)
 
206
        except XenAPI.Failure, exc:
 
207
            logging.warn(exc)
 
208
            deferred.errback(exc)
 
209
 
 
210
 
 
211
def _unwrap_plugin_exceptions(func, *args, **kwargs):
 
212
    """ Parse exception details """
 
213
    try:
 
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'):
 
220
            params = None
 
221
            try:
 
222
                params = eval(exc.details[3])
 
223
            except:
 
224
                raise exc
 
225
            raise XenAPI.Failure(params)
 
226
        else:
 
227
            raise
 
228
    except xmlrpclib.ProtocolError, exc:
 
229
        logging.debug("Got exception: %s", exc)
 
230
        raise
 
231
 
 
232
 
 
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."""
 
236
    if not val:
 
237
        return val
 
238
    x = xmlrpclib.loads(
 
239
        '<?xml version="1.0"?><methodResponse><params><param>' +
 
240
        val +
 
241
        '</param></params></methodResponse>')
 
242
    return x[0][0]