237 lines
9.9 KiB
Python
237 lines
9.9 KiB
Python
# Copyright (c) 2013 VMware, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""
|
|
Classes for making VMware VI SOAP calls.
|
|
"""
|
|
|
|
import httplib
|
|
import suds
|
|
|
|
from cinder.volume.drivers.vmware import error_util
|
|
|
|
RESP_NOT_XML_ERROR = "Response is 'text/html', not 'text/xml'"
|
|
CONN_ABORT_ERROR = 'Software caused connection abort'
|
|
ADDRESS_IN_USE_ERROR = 'Address already in use'
|
|
|
|
|
|
def get_moref(value, type):
|
|
"""Get managed object reference.
|
|
|
|
:param value: value for the managed object
|
|
:param type: type of the managed object
|
|
:return: Managed object reference with with input value and type
|
|
"""
|
|
moref = suds.sudsobject.Property(value)
|
|
moref._type = type
|
|
return moref
|
|
|
|
|
|
class VIMMessagePlugin(suds.plugin.MessagePlugin):
|
|
|
|
def addAttributeForValue(self, node):
|
|
"""Helper to handle AnyType.
|
|
|
|
suds does not handle AnyType properly.
|
|
VI SDK requires type attribute to be set when AnyType is used
|
|
|
|
:param node: XML value node
|
|
"""
|
|
if node.name == 'value':
|
|
node.set('xsi:type', 'xsd:string')
|
|
|
|
def marshalled(self, context):
|
|
"""Marshal soap context.
|
|
|
|
Provides the plugin with the opportunity to prune empty
|
|
nodes and fixup nodes before sending it to the server.
|
|
|
|
:param context: SOAP context
|
|
"""
|
|
# suds builds the entire request object based on the wsdl schema.
|
|
# VI SDK throws server errors if optional SOAP nodes are sent
|
|
# without values, e.g. <test/> as opposed to <test>test</test>
|
|
context.envelope.prune()
|
|
context.envelope.walk(self.addAttributeForValue)
|
|
|
|
|
|
class Vim(object):
|
|
"""The VIM Object."""
|
|
|
|
def __init__(self, protocol='https', host='localhost', wsdl_loc=None):
|
|
"""Create communication interfaces for initiating SOAP transactions.
|
|
|
|
:param protocol: http or https
|
|
:param host: Server IPAddress[:port] or Hostname[:port]
|
|
"""
|
|
self._protocol = protocol
|
|
self._host_name = host
|
|
if not wsdl_loc:
|
|
wsdl_loc = Vim._get_wsdl_loc(protocol, host)
|
|
soap_url = Vim._get_soap_url(protocol, host)
|
|
self._client = suds.client.Client(wsdl_loc, location=soap_url,
|
|
plugins=[VIMMessagePlugin()])
|
|
self._service_content = self.RetrieveServiceContent('ServiceInstance')
|
|
|
|
@staticmethod
|
|
def _get_wsdl_loc(protocol, host_name):
|
|
"""Return default WSDL file location hosted at the server.
|
|
|
|
:param protocol: http or https
|
|
:param host_name: ESX/VC server host name
|
|
:return: Default WSDL file location hosted at the server
|
|
"""
|
|
return '%s://%s/sdk/vimService.wsdl' % (protocol, host_name)
|
|
|
|
@staticmethod
|
|
def _get_soap_url(protocol, host_name):
|
|
"""Return URL to SOAP services for ESX/VC server.
|
|
|
|
:param protocol: https or http
|
|
:param host_name: ESX/VC server host name
|
|
:return: URL to SOAP services for ESX/VC server
|
|
"""
|
|
return '%s://%s/sdk' % (protocol, host_name)
|
|
|
|
@property
|
|
def service_content(self):
|
|
return self._service_content
|
|
|
|
@property
|
|
def client(self):
|
|
return self._client
|
|
|
|
def __getattr__(self, attr_name):
|
|
"""Makes the API call and gets the result."""
|
|
|
|
def retrieve_properties_ex_fault_checker(response):
|
|
"""Checks the RetrievePropertiesEx response for errors.
|
|
|
|
Certain faults are sent as part of the SOAP body as property of
|
|
missingSet. For example NotAuthenticated fault. The method raises
|
|
appropriate VimFaultException when an error is found.
|
|
|
|
:param response: Response from RetrievePropertiesEx API call
|
|
"""
|
|
|
|
fault_list = []
|
|
if not response:
|
|
# This is the case when the session has timed out. ESX SOAP
|
|
# server sends an empty RetrievePropertiesExResponse. Normally
|
|
# missingSet in the returnval field has the specifics about
|
|
# the error, but that's not the case with a timed out idle
|
|
# session. It is as bad as a terminated session for we cannot
|
|
# use the session. So setting fault to NotAuthenticated fault.
|
|
fault_list = [error_util.NOT_AUTHENTICATED]
|
|
else:
|
|
for obj_cont in response:
|
|
if hasattr(obj_cont, 'missingSet'):
|
|
for missing_elem in obj_cont.missingSet:
|
|
fault_type = missing_elem.fault.fault.__class__
|
|
# Fault needs to be added to the type of fault
|
|
# for uniformity in error checking as SOAP faults
|
|
# define
|
|
fault_list.append(fault_type.__name__)
|
|
if fault_list:
|
|
exc_msg_list = ', '.join(fault_list)
|
|
raise error_util.VimFaultException(fault_list,
|
|
_("Error(s): %s occurred "
|
|
"in the call to "
|
|
"RetrievePropertiesEx.") %
|
|
exc_msg_list)
|
|
|
|
def vim_request_handler(managed_object, **kwargs):
|
|
"""Handler for VI SDK calls.
|
|
|
|
Builds the SOAP message and parses the response for fault
|
|
checking and other errors.
|
|
|
|
:param managed_object:Managed object reference
|
|
:param kwargs: Keyword arguments of the call
|
|
:return: Response of the API call
|
|
"""
|
|
|
|
try:
|
|
if isinstance(managed_object, str):
|
|
# For strings use string value for value and type
|
|
# of the managed object.
|
|
managed_object = get_moref(managed_object, managed_object)
|
|
request = getattr(self.client.service, attr_name)
|
|
response = request(managed_object, **kwargs)
|
|
if (attr_name.lower() == 'retrievepropertiesex'):
|
|
retrieve_properties_ex_fault_checker(response)
|
|
return response
|
|
|
|
except error_util.VimFaultException as excep:
|
|
raise
|
|
|
|
except suds.WebFault as excep:
|
|
doc = excep.document
|
|
detail = doc.childAtPath('/Envelope/Body/Fault/detail')
|
|
fault_list = []
|
|
for child in detail.getChildren():
|
|
fault_list.append(child.get('type'))
|
|
raise error_util.VimFaultException(fault_list, excep)
|
|
|
|
except AttributeError as excep:
|
|
raise error_util.VimAttributeException(_("No such SOAP method "
|
|
"%(attr)s. Detailed "
|
|
"error: %(excep)s.") %
|
|
{'attr': attr_name,
|
|
'excep': excep})
|
|
|
|
except (httplib.CannotSendRequest,
|
|
httplib.ResponseNotReady,
|
|
httplib.CannotSendHeader) as excep:
|
|
raise error_util.SessionOverLoadException(_("httplib error in "
|
|
"%(attr)s: "
|
|
"%(excep)s.") %
|
|
{'attr': attr_name,
|
|
'excep': excep})
|
|
|
|
except Exception as excep:
|
|
# Socket errors which need special handling for they
|
|
# might be caused by server API call overload
|
|
if (str(excep).find(ADDRESS_IN_USE_ERROR) != -1 or
|
|
str(excep).find(CONN_ABORT_ERROR)) != -1:
|
|
raise error_util.SessionOverLoadException(_("Socket error "
|
|
"in %(attr)s: "
|
|
"%(excep)s.") %
|
|
{'attr':
|
|
attr_name,
|
|
'excep': excep})
|
|
# Type error that needs special handling for it might be
|
|
# caused by server API call overload
|
|
elif str(excep).find(RESP_NOT_XML_ERROR) != -1:
|
|
raise error_util.SessionOverLoadException(_("Type error "
|
|
"in %(attr)s: "
|
|
"%(excep)s.") %
|
|
{'attr':
|
|
attr_name,
|
|
'excep': excep})
|
|
else:
|
|
raise error_util.VimException(_("Error in %(attr)s. "
|
|
"Detailed error: "
|
|
"%(excep)s.") %
|
|
{'attr': attr_name,
|
|
'excep': excep})
|
|
return vim_request_handler
|
|
|
|
def __repr__(self):
|
|
return "VIM Object."
|
|
|
|
def __str__(self):
|
|
return "VIM Object."
|