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

« back to all changes in this revision

Viewing changes to nova/virt/vmwareapi/driver.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2012-08-16 14:04:11 UTC
  • mto: This revision was merged to the branch mainline in revision 84.
  • Revision ID: package-import@ubuntu.com-20120816140411-0mr4n241wmk30t9l
Tags: upstream-2012.2~f3
ImportĀ upstreamĀ versionĀ 2012.2~f3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright (c) 2011 Citrix Systems, Inc.
 
4
# Copyright 2011 OpenStack LLC.
 
5
#
 
6
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 
7
#    not use this file except in compliance with the License. You may obtain
 
8
#    a copy of the License at
 
9
#
 
10
#         http://www.apache.org/licenses/LICENSE-2.0
 
11
#
 
12
#    Unless required by applicable law or agreed to in writing, software
 
13
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 
14
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 
15
#    License for the specific language governing permissions and limitations
 
16
#    under the License.
 
17
 
 
18
"""
 
19
A connection to the VMware ESX platform.
 
20
 
 
21
**Related Flags**
 
22
 
 
23
:vmwareapi_host_ip:        IPAddress of VMware ESX server.
 
24
:vmwareapi_host_username:  Username for connection to VMware ESX Server.
 
25
:vmwareapi_host_password:  Password for connection to VMware ESX Server.
 
26
:vmwareapi_task_poll_interval:  The interval (seconds) used for polling of
 
27
                             remote tasks
 
28
                             (default: 1.0).
 
29
:vmwareapi_api_retry_count:  The API retry count in case of failure such as
 
30
                             network failures (socket errors etc.)
 
31
                             (default: 10).
 
32
 
 
33
"""
 
34
 
 
35
import time
 
36
 
 
37
from eventlet import event
 
38
 
 
39
from nova import exception
 
40
from nova import flags
 
41
from nova.openstack.common import cfg
 
42
from nova.openstack.common import log as logging
 
43
from nova import utils
 
44
from nova.virt import driver
 
45
from nova.virt.vmwareapi import error_util
 
46
from nova.virt.vmwareapi import vim
 
47
from nova.virt.vmwareapi import vim_util
 
48
from nova.virt.vmwareapi import vmops
 
49
 
 
50
 
 
51
LOG = logging.getLogger(__name__)
 
52
 
 
53
vmwareapi_opts = [
 
54
    cfg.StrOpt('vmwareapi_host_ip',
 
55
               default=None,
 
56
               help='URL for connection to VMWare ESX host.Required if '
 
57
                    'compute_driver is vmwareapi.VMWareESXDriver.'),
 
58
    cfg.StrOpt('vmwareapi_host_username',
 
59
               default=None,
 
60
               help='Username for connection to VMWare ESX host. '
 
61
                    'Used only if compute_driver is '
 
62
                    'vmwareapi.VMWareESXDriver.'),
 
63
    cfg.StrOpt('vmwareapi_host_password',
 
64
               default=None,
 
65
               help='Password for connection to VMWare ESX host. '
 
66
                    'Used only if compute_driver is '
 
67
                    'vmwareapi.VMWareESXDriver.'),
 
68
    cfg.FloatOpt('vmwareapi_task_poll_interval',
 
69
                 default=5.0,
 
70
                 help='The interval used for polling of remote tasks. '
 
71
                       'Used only if compute_driver is '
 
72
                       'vmwareapi.VMWareESXDriver.'),
 
73
    cfg.IntOpt('vmwareapi_api_retry_count',
 
74
               default=10,
 
75
               help='The number of times we retry on failures, e.g., '
 
76
                    'socket error, etc. '
 
77
                    'Used only if compute_driver is '
 
78
                    'vmwareapi.VMWareESXDriver.'),
 
79
    cfg.StrOpt('vmwareapi_vlan_interface',
 
80
               default='vmnic0',
 
81
               help='Physical ethernet adapter name for vlan networking'),
 
82
    ]
 
83
 
 
84
FLAGS = flags.FLAGS
 
85
FLAGS.register_opts(vmwareapi_opts)
 
86
 
 
87
TIME_BETWEEN_API_CALL_RETRIES = 2.0
 
88
 
 
89
 
 
90
class Failure(Exception):
 
91
    """Base Exception class for handling task failures."""
 
92
 
 
93
    def __init__(self, details):
 
94
        self.details = details
 
95
 
 
96
    def __str__(self):
 
97
        return str(self.details)
 
98
 
 
99
 
 
100
class VMWareESXDriver(driver.ComputeDriver):
 
101
    """The ESX host connection object."""
 
102
 
 
103
    def __init__(self, read_only=False, scheme="https"):
 
104
        super(VMWareESXDriver, self).__init__()
 
105
 
 
106
        host_ip = FLAGS.vmwareapi_host_ip
 
107
        host_username = FLAGS.vmwareapi_host_username
 
108
        host_password = FLAGS.vmwareapi_host_password
 
109
        api_retry_count = FLAGS.vmwareapi_api_retry_count
 
110
        if not host_ip or host_username is None or host_password is None:
 
111
            raise Exception(_("Must specify vmwareapi_host_ip,"
 
112
                              "vmwareapi_host_username "
 
113
                              "and vmwareapi_host_password to use"
 
114
                              "compute_driver=vmwareapi.VMWareESXDriver"))
 
115
 
 
116
        session = VMWareAPISession(host_ip, host_username, host_password,
 
117
                                   api_retry_count, scheme=scheme)
 
118
        self._vmops = vmops.VMWareVMOps(session)
 
119
 
 
120
    def init_host(self, host):
 
121
        """Do the initialization that needs to be done."""
 
122
        # FIXME(sateesh): implement this
 
123
        pass
 
124
 
 
125
    def list_instances(self):
 
126
        """List VM instances."""
 
127
        return self._vmops.list_instances()
 
128
 
 
129
    def spawn(self, context, instance, image_meta, network_info,
 
130
              block_device_mapping=None):
 
131
        """Create VM instance."""
 
132
        self._vmops.spawn(context, instance, image_meta, network_info)
 
133
 
 
134
    def snapshot(self, context, instance, name):
 
135
        """Create snapshot from a running VM instance."""
 
136
        self._vmops.snapshot(context, instance, name)
 
137
 
 
138
    def reboot(self, instance, network_info, reboot_type):
 
139
        """Reboot VM instance."""
 
140
        self._vmops.reboot(instance, network_info)
 
141
 
 
142
    def destroy(self, instance, network_info, block_device_info=None):
 
143
        """Destroy VM instance."""
 
144
        self._vmops.destroy(instance, network_info)
 
145
 
 
146
    def pause(self, instance):
 
147
        """Pause VM instance."""
 
148
        self._vmops.pause(instance)
 
149
 
 
150
    def unpause(self, instance):
 
151
        """Unpause paused VM instance."""
 
152
        self._vmops.unpause(instance)
 
153
 
 
154
    def suspend(self, instance):
 
155
        """Suspend the specified instance."""
 
156
        self._vmops.suspend(instance)
 
157
 
 
158
    def resume(self, instance):
 
159
        """Resume the suspended VM instance."""
 
160
        self._vmops.resume(instance)
 
161
 
 
162
    def get_info(self, instance):
 
163
        """Return info about the VM instance."""
 
164
        return self._vmops.get_info(instance)
 
165
 
 
166
    def get_diagnostics(self, instance):
 
167
        """Return data about VM diagnostics."""
 
168
        return self._vmops.get_info(instance)
 
169
 
 
170
    def get_console_output(self, instance):
 
171
        """Return snapshot of console."""
 
172
        return self._vmops.get_console_output(instance)
 
173
 
 
174
    def get_volume_connector(self, _instance):
 
175
        """Return volume connector information"""
 
176
        # TODO(vish): When volume attaching is supported, return the
 
177
        #             proper initiator iqn and host.
 
178
        return {
 
179
            'ip': FLAGS.vmwareapi_host_ip,
 
180
            'initiator': None,
 
181
            'host': None
 
182
        }
 
183
 
 
184
    def attach_volume(self, connection_info, instance_name, mountpoint):
 
185
        """Attach volume storage to VM instance."""
 
186
        pass
 
187
 
 
188
    def detach_volume(self, connection_info, instance_name, mountpoint):
 
189
        """Detach volume storage to VM instance."""
 
190
        pass
 
191
 
 
192
    def get_console_pool_info(self, console_type):
 
193
        """Get info about the host on which the VM resides."""
 
194
        return {'address': FLAGS.vmwareapi_host_ip,
 
195
                'username': FLAGS.vmwareapi_host_username,
 
196
                'password': FLAGS.vmwareapi_host_password}
 
197
 
 
198
    def update_available_resource(self, ctxt, host):
 
199
        """This method is supported only by libvirt."""
 
200
        return
 
201
 
 
202
    def host_power_action(self, host, action):
 
203
        """Reboots, shuts down or powers up the host."""
 
204
        raise NotImplementedError()
 
205
 
 
206
    def host_maintenance_mode(self, host, mode):
 
207
        """Start/Stop host maintenance window. On start, it triggers
 
208
        guest VMs evacuation."""
 
209
        raise NotImplementedError()
 
210
 
 
211
    def set_host_enabled(self, host, enabled):
 
212
        """Sets the specified host's ability to accept new instances."""
 
213
        raise NotImplementedError()
 
214
 
 
215
    def plug_vifs(self, instance, network_info):
 
216
        """Plug VIFs into networks."""
 
217
        self._vmops.plug_vifs(instance, network_info)
 
218
 
 
219
    def unplug_vifs(self, instance, network_info):
 
220
        """Unplug VIFs from networks."""
 
221
        self._vmops.unplug_vifs(instance, network_info)
 
222
 
 
223
 
 
224
class VMWareAPISession(object):
 
225
    """
 
226
    Sets up a session with the ESX host and handles all
 
227
    the calls made to the host.
 
228
    """
 
229
 
 
230
    def __init__(self, host_ip, host_username, host_password,
 
231
                 api_retry_count, scheme="https"):
 
232
        self._host_ip = host_ip
 
233
        self._host_username = host_username
 
234
        self._host_password = host_password
 
235
        self.api_retry_count = api_retry_count
 
236
        self._scheme = scheme
 
237
        self._session_id = None
 
238
        self.vim = None
 
239
        self._create_session()
 
240
 
 
241
    def _get_vim_object(self):
 
242
        """Create the VIM Object instance."""
 
243
        return vim.Vim(protocol=self._scheme, host=self._host_ip)
 
244
 
 
245
    def _create_session(self):
 
246
        """Creates a session with the ESX host."""
 
247
        while True:
 
248
            try:
 
249
                # Login and setup the session with the ESX host for making
 
250
                # API calls
 
251
                self.vim = self._get_vim_object()
 
252
                session = self.vim.Login(
 
253
                               self.vim.get_service_content().sessionManager,
 
254
                               userName=self._host_username,
 
255
                               password=self._host_password)
 
256
                # Terminate the earlier session, if possible ( For the sake of
 
257
                # preserving sessions as there is a limit to the number of
 
258
                # sessions we can have )
 
259
                if self._session_id:
 
260
                    try:
 
261
                        self.vim.TerminateSession(
 
262
                                self.vim.get_service_content().sessionManager,
 
263
                                sessionId=[self._session_id])
 
264
                    except Exception, excep:
 
265
                        # This exception is something we can live with. It is
 
266
                        # just an extra caution on our side. The session may
 
267
                        # have been cleared. We could have made a call to
 
268
                        # SessionIsActive, but that is an overhead because we
 
269
                        # anyway would have to call TerminateSession.
 
270
                        LOG.debug(excep)
 
271
                self._session_id = session.key
 
272
                return
 
273
            except Exception, excep:
 
274
                LOG.critical(_("In vmwareapi:_create_session, "
 
275
                              "got this exception: %s") % excep)
 
276
                raise exception.NovaException(excep)
 
277
 
 
278
    def __del__(self):
 
279
        """Logs-out the session."""
 
280
        # Logout to avoid un-necessary increase in session count at the
 
281
        # ESX host
 
282
        try:
 
283
            self.vim.Logout(self.vim.get_service_content().sessionManager)
 
284
        except Exception, excep:
 
285
            # It is just cautionary on our part to do a logout in del just
 
286
            # to ensure that the session is not left active.
 
287
            LOG.debug(excep)
 
288
 
 
289
    def _is_vim_object(self, module):
 
290
        """Check if the module is a VIM Object instance."""
 
291
        return isinstance(module, vim.Vim)
 
292
 
 
293
    def _call_method(self, module, method, *args, **kwargs):
 
294
        """
 
295
        Calls a method within the module specified with
 
296
        args provided.
 
297
        """
 
298
        args = list(args)
 
299
        retry_count = 0
 
300
        exc = None
 
301
        last_fault_list = []
 
302
        while True:
 
303
            try:
 
304
                if not self._is_vim_object(module):
 
305
                    # If it is not the first try, then get the latest
 
306
                    # vim object
 
307
                    if retry_count > 0:
 
308
                        args = args[1:]
 
309
                    args = [self.vim] + args
 
310
                retry_count += 1
 
311
                temp_module = module
 
312
 
 
313
                for method_elem in method.split("."):
 
314
                    temp_module = getattr(temp_module, method_elem)
 
315
 
 
316
                return temp_module(*args, **kwargs)
 
317
            except error_util.VimFaultException, excep:
 
318
                # If it is a Session Fault Exception, it may point
 
319
                # to a session gone bad. So we try re-creating a session
 
320
                # and then proceeding ahead with the call.
 
321
                exc = excep
 
322
                if error_util.FAULT_NOT_AUTHENTICATED in excep.fault_list:
 
323
                    # Because of the idle session returning an empty
 
324
                    # RetrievePropertiesResponse and also the same is returned
 
325
                    # when there is say empty answer to the query for
 
326
                    # VMs on the host ( as in no VMs on the host), we have no
 
327
                    # way to differentiate.
 
328
                    # So if the previous response was also am empty response
 
329
                    # and after creating a new session, we get the same empty
 
330
                    # response, then we are sure of the response being supposed
 
331
                    # to be empty.
 
332
                    if error_util.FAULT_NOT_AUTHENTICATED in last_fault_list:
 
333
                        return []
 
334
                    last_fault_list = excep.fault_list
 
335
                    self._create_session()
 
336
                else:
 
337
                    # No re-trying for errors for API call has gone through
 
338
                    # and is the caller's fault. Caller should handle these
 
339
                    # errors. e.g, InvalidArgument fault.
 
340
                    break
 
341
            except error_util.SessionOverLoadException, excep:
 
342
                # For exceptions which may come because of session overload,
 
343
                # we retry
 
344
                exc = excep
 
345
            except Exception, excep:
 
346
                # If it is a proper exception, say not having furnished
 
347
                # proper data in the SOAP call or the retry limit having
 
348
                # exceeded, we raise the exception
 
349
                exc = excep
 
350
                break
 
351
            # If retry count has been reached then break and
 
352
            # raise the exception
 
353
            if retry_count > self.api_retry_count:
 
354
                break
 
355
            time.sleep(TIME_BETWEEN_API_CALL_RETRIES)
 
356
 
 
357
        LOG.critical(_("In vmwareapi:_call_method, "
 
358
                     "got this exception: %s") % exc)
 
359
        raise
 
360
 
 
361
    def _get_vim(self):
 
362
        """Gets the VIM object reference."""
 
363
        if self.vim is None:
 
364
            self._create_session()
 
365
        return self.vim
 
366
 
 
367
    def _wait_for_task(self, instance_uuid, task_ref):
 
368
        """
 
369
        Return a Deferred that will give the result of the given task.
 
370
        The task is polled until it completes.
 
371
        """
 
372
        done = event.Event()
 
373
        loop = utils.LoopingCall(self._poll_task, instance_uuid, task_ref,
 
374
                                      done)
 
375
        loop.start(FLAGS.vmwareapi_task_poll_interval)
 
376
        ret_val = done.wait()
 
377
        loop.stop()
 
378
        return ret_val
 
379
 
 
380
    def _poll_task(self, instance_uuid, task_ref, done):
 
381
        """
 
382
        Poll the given task, and fires the given Deferred if we
 
383
        get a result.
 
384
        """
 
385
        try:
 
386
            task_info = self._call_method(vim_util, "get_dynamic_property",
 
387
                            task_ref, "Task", "info")
 
388
            task_name = task_info.name
 
389
            if task_info.state in ['queued', 'running']:
 
390
                return
 
391
            elif task_info.state == 'success':
 
392
                LOG.debug(_("Task [%(task_name)s] %(task_ref)s "
 
393
                            "status: success") % locals())
 
394
                done.send("success")
 
395
            else:
 
396
                error_info = str(task_info.error.localizedMessage)
 
397
                LOG.warn(_("Task [%(task_name)s] %(task_ref)s "
 
398
                          "status: error %(error_info)s") % locals())
 
399
                done.send_exception(exception.NovaException(error_info))
 
400
        except Exception, excep:
 
401
            LOG.warn(_("In vmwareapi:_poll_task, Got this error %s") % excep)
 
402
            done.send_exception(excep)