1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright (c) 2011 Citrix Systems, Inc.
4
# Copyright 2011 OpenStack LLC.
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
10
# http://www.apache.org/licenses/LICENSE-2.0
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
19
A connection to the VMware ESX platform.
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
29
:vmwareapi_api_retry_count: The API retry count in case of failure such as
30
network failures (socket errors etc.)
37
from eventlet import event
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
51
LOG = logging.getLogger(__name__)
54
cfg.StrOpt('vmwareapi_host_ip',
56
help='URL for connection to VMWare ESX host.Required if '
57
'compute_driver is vmwareapi.VMWareESXDriver.'),
58
cfg.StrOpt('vmwareapi_host_username',
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',
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',
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',
75
help='The number of times we retry on failures, e.g., '
77
'Used only if compute_driver is '
78
'vmwareapi.VMWareESXDriver.'),
79
cfg.StrOpt('vmwareapi_vlan_interface',
81
help='Physical ethernet adapter name for vlan networking'),
85
FLAGS.register_opts(vmwareapi_opts)
87
TIME_BETWEEN_API_CALL_RETRIES = 2.0
90
class Failure(Exception):
91
"""Base Exception class for handling task failures."""
93
def __init__(self, details):
94
self.details = details
97
return str(self.details)
100
class VMWareESXDriver(driver.ComputeDriver):
101
"""The ESX host connection object."""
103
def __init__(self, read_only=False, scheme="https"):
104
super(VMWareESXDriver, self).__init__()
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"))
116
session = VMWareAPISession(host_ip, host_username, host_password,
117
api_retry_count, scheme=scheme)
118
self._vmops = vmops.VMWareVMOps(session)
120
def init_host(self, host):
121
"""Do the initialization that needs to be done."""
122
# FIXME(sateesh): implement this
125
def list_instances(self):
126
"""List VM instances."""
127
return self._vmops.list_instances()
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)
134
def snapshot(self, context, instance, name):
135
"""Create snapshot from a running VM instance."""
136
self._vmops.snapshot(context, instance, name)
138
def reboot(self, instance, network_info, reboot_type):
139
"""Reboot VM instance."""
140
self._vmops.reboot(instance, network_info)
142
def destroy(self, instance, network_info, block_device_info=None):
143
"""Destroy VM instance."""
144
self._vmops.destroy(instance, network_info)
146
def pause(self, instance):
147
"""Pause VM instance."""
148
self._vmops.pause(instance)
150
def unpause(self, instance):
151
"""Unpause paused VM instance."""
152
self._vmops.unpause(instance)
154
def suspend(self, instance):
155
"""Suspend the specified instance."""
156
self._vmops.suspend(instance)
158
def resume(self, instance):
159
"""Resume the suspended VM instance."""
160
self._vmops.resume(instance)
162
def get_info(self, instance):
163
"""Return info about the VM instance."""
164
return self._vmops.get_info(instance)
166
def get_diagnostics(self, instance):
167
"""Return data about VM diagnostics."""
168
return self._vmops.get_info(instance)
170
def get_console_output(self, instance):
171
"""Return snapshot of console."""
172
return self._vmops.get_console_output(instance)
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.
179
'ip': FLAGS.vmwareapi_host_ip,
184
def attach_volume(self, connection_info, instance_name, mountpoint):
185
"""Attach volume storage to VM instance."""
188
def detach_volume(self, connection_info, instance_name, mountpoint):
189
"""Detach volume storage to VM instance."""
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}
198
def update_available_resource(self, ctxt, host):
199
"""This method is supported only by libvirt."""
202
def host_power_action(self, host, action):
203
"""Reboots, shuts down or powers up the host."""
204
raise NotImplementedError()
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()
211
def set_host_enabled(self, host, enabled):
212
"""Sets the specified host's ability to accept new instances."""
213
raise NotImplementedError()
215
def plug_vifs(self, instance, network_info):
216
"""Plug VIFs into networks."""
217
self._vmops.plug_vifs(instance, network_info)
219
def unplug_vifs(self, instance, network_info):
220
"""Unplug VIFs from networks."""
221
self._vmops.unplug_vifs(instance, network_info)
224
class VMWareAPISession(object):
226
Sets up a session with the ESX host and handles all
227
the calls made to the host.
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
239
self._create_session()
241
def _get_vim_object(self):
242
"""Create the VIM Object instance."""
243
return vim.Vim(protocol=self._scheme, host=self._host_ip)
245
def _create_session(self):
246
"""Creates a session with the ESX host."""
249
# Login and setup the session with the ESX host for making
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 )
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.
271
self._session_id = session.key
273
except Exception, excep:
274
LOG.critical(_("In vmwareapi:_create_session, "
275
"got this exception: %s") % excep)
276
raise exception.NovaException(excep)
279
"""Logs-out the session."""
280
# Logout to avoid un-necessary increase in session count at the
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.
289
def _is_vim_object(self, module):
290
"""Check if the module is a VIM Object instance."""
291
return isinstance(module, vim.Vim)
293
def _call_method(self, module, method, *args, **kwargs):
295
Calls a method within the module specified with
304
if not self._is_vim_object(module):
305
# If it is not the first try, then get the latest
309
args = [self.vim] + args
313
for method_elem in method.split("."):
314
temp_module = getattr(temp_module, method_elem)
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.
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
332
if error_util.FAULT_NOT_AUTHENTICATED in last_fault_list:
334
last_fault_list = excep.fault_list
335
self._create_session()
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.
341
except error_util.SessionOverLoadException, excep:
342
# For exceptions which may come because of session overload,
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
351
# If retry count has been reached then break and
352
# raise the exception
353
if retry_count > self.api_retry_count:
355
time.sleep(TIME_BETWEEN_API_CALL_RETRIES)
357
LOG.critical(_("In vmwareapi:_call_method, "
358
"got this exception: %s") % exc)
362
"""Gets the VIM object reference."""
364
self._create_session()
367
def _wait_for_task(self, instance_uuid, task_ref):
369
Return a Deferred that will give the result of the given task.
370
The task is polled until it completes.
373
loop = utils.LoopingCall(self._poll_task, instance_uuid, task_ref,
375
loop.start(FLAGS.vmwareapi_task_poll_interval)
376
ret_val = done.wait()
380
def _poll_task(self, instance_uuid, task_ref, done):
382
Poll the given task, and fires the given Deferred if we
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']:
391
elif task_info.state == 'success':
392
LOG.debug(_("Task [%(task_name)s] %(task_ref)s "
393
"status: success") % locals())
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)