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

« back to all changes in this revision

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