From 3e97dc47221d19b39aba99f6d389d2ec326e72be Mon Sep 17 00:00:00 2001 From: sateesh Date: Thu, 10 Mar 2011 21:07:44 +0530 Subject: Updated the code to detect the exception by fault type. SOAP faults are embedded in the SOAP response as a property. Certain faults are sent as a part of the SOAP body as property of missingSet. E.g. NotAuthenticated fault. So we examine the response object for missingSet and try to check the property for fault type. --- nova/console/vmrc.py | 2 +- nova/virt/vmwareapi/error_util.py | 82 ++++++++++++++++++++++++++++++++++++ nova/virt/vmwareapi/fake.py | 13 +++--- nova/virt/vmwareapi/network_utils.py | 44 +++++++++++++------ nova/virt/vmwareapi/vim.py | 69 ++++++++++++++---------------- nova/virt/vmwareapi/vmops.py | 13 ++++-- nova/virt/vmwareapi_conn.py | 23 ++++++---- 7 files changed, 178 insertions(+), 68 deletions(-) create mode 100644 nova/virt/vmwareapi/error_util.py diff --git a/nova/console/vmrc.py b/nova/console/vmrc.py index 09f8067e5..9c5e3b444 100644 --- a/nova/console/vmrc.py +++ b/nova/console/vmrc.py @@ -119,7 +119,7 @@ class VMRCSessionConsole(VMRCConsole): vim_session._get_vim(), "AcquireCloneTicket", vim_session._get_vim().get_service_content().sessionManager) - return str(vm_ref) + ":" + virtual_machine_ticket + return str(vm_ref.value) + ":" + virtual_machine_ticket def is_otp(self): """Is one time password.""" diff --git a/nova/virt/vmwareapi/error_util.py b/nova/virt/vmwareapi/error_util.py new file mode 100644 index 000000000..3196b5038 --- /dev/null +++ b/nova/virt/vmwareapi/error_util.py @@ -0,0 +1,82 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Citrix Systems, Inc. +# Copyright 2011 OpenStack LLC. +# +# 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. + +""" +Exception classes and SOAP response error checking module +""" + +FAULT_NOT_AUTHENTICATED = "NotAuthenticated" +FAULT_ALREADY_EXISTS = "AlreadyExists" + + +class VimException(Exception): + """The VIM Exception class""" + + def __init__(self, exception_summary, excep): + Exception.__init__(self) + self.exception_summary = exception_summary + self.exception_obj = excep + + def __str__(self): + return self.exception_summary + str(self.exception_obj) + + +class SessionOverLoadException(VimException): + """Session Overload Exception""" + pass + + +class VimAttributeError(VimException): + """VI Attribute Error""" + pass + + +class VimFaultException(Exception): + """The VIM Fault exception class""" + + def __init__(self, fault_list, excep): + Exception.__init__(self) + self.fault_list = fault_list + self.exception_obj = excep + + def __str__(self): + return str(self.exception_obj) + + +class FaultCheckers: + """Methods for fault checking of SOAP response. Per Method error handlers + for which we desire error checking are defined. SOAP faults are + embedded in the SOAP as a property and not as a SOAP fault.""" + + @classmethod + def retrieveproperties_fault_checker(self, resp_obj): + """Checks the RetrieveProperties response for errors. Certain faults + are sent as a part of the SOAP body as property of missingSet. + For example NotAuthenticated fault""" + fault_list = [] + for obj_cont in resp_obj: + if hasattr(obj_cont, "missingSet"): + for missing_elem in obj_cont.missingSet: + fault_type = missing_elem.fault.fault.__class__.__name__ + #Fault needs to be added to the type of fault for + #uniformity in error checking as SOAP faults define + fault_list.append(fault_type) + if fault_list: + exc_msg_list = ', '.join(fault_list) + raise VimFaultException(fault_list, Exception(_("Error(s) %s " + "occurred in the call to RetrieveProperties") % + exc_msg_list)) diff --git a/nova/virt/vmwareapi/fake.py b/nova/virt/vmwareapi/fake.py index 40ed18340..909d1a6cf 100644 --- a/nova/virt/vmwareapi/fake.py +++ b/nova/virt/vmwareapi/fake.py @@ -25,7 +25,7 @@ import uuid from nova import exception from nova import log as logging from nova.virt.vmwareapi import vim -from nova.virt.vmwareapi.vim import SessionFaultyException +from nova.virt.vmwareapi import error_util _CLASSES = ['Datacenter', 'Datastore', 'ResourcePool', 'VirtualMachine', 'Network', 'HostSystem', 'HostNetworkSystem', 'Task', 'session', @@ -500,7 +500,7 @@ class FakeVim(object): "out: %s") % s) del _db_content['session'][s] - def _terminate(self, *args, **kwargs): + def _terminate_session(self, *args, **kwargs): """ Terminates a session """ s = kwargs.get("sessionId")[0] if s not in _db_content['session']: @@ -512,7 +512,9 @@ class FakeVim(object): if (self._session is None or self._session not in _db_content['session']): LOG.debug(_("Session is faulty")) - raise SessionFaultyException(_("Session Invalid")) + raise error_util.VimFaultException( + [error_util.FAULT_NOT_AUTHENTICATED], + _("Session Invalid")) def _create_vm(self, method, *args, **kwargs): """ Creates and registers a VM object with the Host System """ @@ -656,8 +658,9 @@ class FakeVim(object): return lambda *args, **kwargs: self._login() elif attr_name == "Logout": self._logout() - elif attr_name == "Terminate": - return lambda *args, **kwargs: self._terminate(*args, **kwargs) + elif attr_name == "TerminateSession": + return lambda *args, **kwargs: self._terminate_session( + *args, **kwargs) elif attr_name == "CreateVM_Task": return lambda *args, **kwargs: self._create_vm(attr_name, *args, **kwargs) diff --git a/nova/virt/vmwareapi/network_utils.py b/nova/virt/vmwareapi/network_utils.py index 59a3c234f..f27121071 100644 --- a/nova/virt/vmwareapi/network_utils.py +++ b/nova/virt/vmwareapi/network_utils.py @@ -20,15 +20,12 @@ Utility functions for ESX Networking """ from nova import log as logging +from nova.virt.vmwareapi import error_util from nova.virt.vmwareapi import vim_util from nova.virt.vmwareapi import vm_util -from nova.virt.vmwareapi.vim import VimException LOG = logging.getLogger("nova.virt.vmwareapi.network_utils") -PORT_GROUP_EXISTS_EXCEPTION = \ - 'The specified key, name, or identifier already exists.' - class NetworkHelper: @@ -38,7 +35,13 @@ class NetworkHelper: argument. """ datacenters = session._call_method(vim_util, "get_objects", "Datacenter", ["network"]) - vm_networks = datacenters[0].propSet[0].val.ManagedObjectReference + vm_networks_ret = datacenters[0].propSet[0].val + #Meaning there are no networks on the host. suds responds with a "" + #in the parent property field rather than a [] in the + #ManagedObjectRefernce property field of the parent + if not vm_networks_ret: + return None + vm_networks = vm_networks_ret.ManagedObjectReference networks = session._call_method(vim_util, "get_properites_for_a_collection_of_objects", "Network", vm_networks, ["summary.name"]) @@ -54,9 +57,14 @@ class NetworkHelper: #Get the list of vSwicthes on the Host System host_mor = session._call_method(vim_util, "get_objects", "HostSystem")[0].obj - vswitches = session._call_method(vim_util, + vswitches_ret = session._call_method(vim_util, "get_dynamic_property", host_mor, - "HostSystem", "config.network.vswitch").HostVirtualSwitch + "HostSystem", "config.network.vswitch") + #Meaning there are no vSwitches on the host. Shouldn't be the case, + #but just doing code check + if not vswitches_ret: + return + vswitches = vswitches_ret.HostVirtualSwitch #Get the vSwitch associated with the network adapter for elem in vswitches: try: @@ -71,9 +79,13 @@ class NetworkHelper: """ Checks if the vlan_inteface exists on the esx host """ host_net_system_mor = session._call_method(vim_util, "get_objects", "HostSystem", ["configManager.networkSystem"])[0].propSet[0].val - physical_nics = session._call_method(vim_util, + physical_nics_ret = session._call_method(vim_util, "get_dynamic_property", host_net_system_mor, - "HostNetworkSystem", "networkInfo.pnic").PhysicalNic + "HostNetworkSystem", "networkInfo.pnic") + #Meaning there are no physical nics on the host + if not physical_nics_ret: + return False + physical_nics = physical_nics_ret.PhysicalNic for pnic in physical_nics: if vlan_interface == pnic.device: return True @@ -84,9 +96,15 @@ class NetworkHelper: """ Get the vlan id and vswicth associated with the port group """ host_mor = session._call_method(vim_util, "get_objects", "HostSystem")[0].obj - port_grps_on_host = session._call_method(vim_util, + port_grps_on_host_ret = session._call_method(vim_util, "get_dynamic_property", host_mor, - "HostSystem", "config.network.portgroup").HostPortGroup + "HostSystem", "config.network.portgroup") + if not port_grps_on_host_ret: + excep = ("ESX SOAP server returned an empty port group " + "for the host system in its response") + LOG.exception(excep) + raise Exception(_(excep)) + port_grps_on_host = port_grps_on_host_ret.HostPortGroup for p_gp in port_grps_on_host: if p_gp.spec.name == pg_name: p_grp_vswitch_name = p_gp.vswitch.split("-")[-1] @@ -113,13 +131,13 @@ class NetworkHelper: session._call_method(session._get_vim(), "AddPortGroup", network_system_mor, portgrp=add_prt_grp_spec) - except VimException, exc: + except error_util.VimFaultException, exc: #There can be a race condition when two instances try #adding port groups at the same time. One succeeds, then #the other one will get an exception. Since we are #concerned with the port group being created, which is done #by the other call, we can ignore the exception. - if str(exc).find(PORT_GROUP_EXISTS_EXCEPTION) == -1: + if error_util.FAULT_ALREADY_EXISTS not in exc.fault_list: raise Exception(exc) LOG.debug(_("Created Port Group with name %s on " "the ESX host") % pg_name) diff --git a/nova/virt/vmwareapi/vim.py b/nova/virt/vmwareapi/vim.py index 6a3e4b376..cea65e198 100644 --- a/nova/virt/vmwareapi/vim.py +++ b/nova/virt/vmwareapi/vim.py @@ -21,11 +21,13 @@ Classes for making VMware VI SOAP calls import httplib +from suds import WebFault from suds.client import Client from suds.plugin import MessagePlugin from suds.sudsobject import Property from nova import flags +from nova.virt.vmwareapi import error_util RESP_NOT_XML_ERROR = 'Response is "text/html", not "text/xml' CONN_ABORT_ERROR = 'Software caused connection abort' @@ -40,33 +42,6 @@ flags.DEFINE_string('vmwareapi_wsdl_loc', 'Read the readme for vmware to setup') -class VimException(Exception): - """The VIM Exception class""" - - def __init__(self, exception_summary, excep): - Exception.__init__(self) - self.exception_summary = exception_summary - self.exception_obj = excep - - def __str__(self): - return self.exception_summary + str(self.exception_obj) - - -class SessionOverLoadException(VimException): - """Session Overload Exception""" - pass - - -class SessionFaultyException(VimException): - """Session Faulty Exception""" - pass - - -class VimAttributeError(VimException): - """VI Attribute Error""" - pass - - class VIMMessagePlugin(MessagePlugin): def addAttributeForValue(self, node): @@ -133,29 +108,49 @@ class Vim: request_mo = \ self._request_managed_object_builder(managed_object) request = getattr(self.client.service, attr_name) - return request(request_mo, **kwargs) + response = request(request_mo, **kwargs) + #To check for the faults that are part of the message body + #and not returned as Fault object response from the ESX + #SOAP server + if hasattr(error_util.FaultCheckers, + attr_name.lower() + "_fault_checker"): + fault_checker = getattr(error_util.FaultCheckers, + attr_name.lower() + "_fault_checker") + fault_checker(response) + return response + #Catch the VimFaultException that is raised by the fault + #check of the SOAP response + except error_util.VimFaultException, excep: + raise + except WebFault, 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, excep: - raise VimAttributeError(_("No such SOAP method '%s'" - " provided by VI SDK") % (attr_name), excep) + raise error_util.VimAttributeError(_("No such SOAP method " + "'%s' provided by VI SDK") % (attr_name), excep) except (httplib.CannotSendRequest, httplib.ResponseNotReady, httplib.CannotSendHeader), excep: - raise SessionOverLoadException(_("httplib error in" - " %s: ") % (attr_name), excep) + raise error_util.SessionOverLoadException(_("httplib " + "error in %s: ") % (attr_name), excep) except Exception, excep: # Socket errors which need special handling for they # might be caused by ESX API call overload if (str(excep).find(ADDRESS_IN_USE_ERROR) != -1 or str(excep).find(CONN_ABORT_ERROR)) != -1: - raise SessionOverLoadException(_("Socket error in" - " %s: ") % (attr_name), excep) + raise error_util.SessionOverLoadException(_("Socket " + "error in %s: ") % (attr_name), excep) # Type error that needs special handling for it might be # caused by ESX host API call overload elif str(excep).find(RESP_NOT_XML_ERROR) != -1: - raise SessionOverLoadException(_("Type error in " - " %s: ") % (attr_name), excep) + raise error_util.SessionOverLoadException(_("Type " + "error in %s: ") % (attr_name), excep) else: - raise VimException( + raise error_util.VimException( _("Exception in %s ") % (attr_name), excep) return vim_request_handler diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index 524c35af5..344c4518b 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -373,10 +373,15 @@ class VMWareVMOps(object): def _check_if_tmp_folder_exists(): #Copy the contents of the VM that were there just before the #snapshot was taken - ds_ref = vim_util.get_dynamic_property(self._session._get_vim(), - vm_ref, - "VirtualMachine", - "datastore").ManagedObjectReference[0] + ds_ref_ret = vim_util.get_dynamic_property( + self._session._get_vim(), + vm_ref, + "VirtualMachine", + "datastore") + if not ds_ref_ret: + raise Exception(_("Failed to get the datastore reference(s) " + "which the VM uses")) + ds_ref = ds_ref_ret.ManagedObjectReference[0] ds_browser = vim_util.get_dynamic_property( self._session._get_vim(), ds_ref, diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index bd3ab4320..a2609278d 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -40,6 +40,7 @@ from nova import db from nova import flags from nova import log as logging from nova import utils +from nova.virt.vmwareapi import error_util from nova.virt.vmwareapi import vim from nova.virt.vmwareapi import vim_util from nova.virt.vmwareapi.vmops import VMWareVMOps @@ -60,7 +61,7 @@ flags.DEFINE_string('vmwareapi_host_password', 'Password for connection to VMWare ESX host.' 'Used only if connection_type is vmwareapi.') flags.DEFINE_float('vmwareapi_task_poll_interval', - 1.0, + 5.0, 'The interval used for polling of remote tasks ' 'Used only if connection_type is vmwareapi') flags.DEFINE_float('vmwareapi_api_retry_count', @@ -264,13 +265,19 @@ class VMWareAPISession(object): ret_val = temp_module(*args, **kwargs) return ret_val - except vim.SessionFaultyException, excep: + except error_util.VimFaultException, excep: # If it is a Session Fault Exception, it may point # to a session gone bad. So we try re-creating a session # and then proceeding ahead with the call. exc = excep - self._create_session() - except vim.SessionOverLoadException, excep: + if error_util.FAULT_NOT_AUTHENTICATED in excep.fault_list: + self._create_session() + else: + #No re-trying for errors for API call has gone through + #and is the caller's fault. Caller should handle these + #errors. e.g, InvalidArgument fault. + break + except error_util.SessionOverLoadException, excep: # For exceptions which may come because of session overload, # we retry exc = excep @@ -288,7 +295,7 @@ class VMWareAPISession(object): LOG.critical(_("In vmwareapi:_call_method, " "got this exception: %s") % exc) - raise Exception(exc) + raise def _get_vim(self): """Gets the VIM object reference""" @@ -301,11 +308,11 @@ class VMWareAPISession(object): The task is polled until it completes. """ done = event.Event() - self.loop = utils.LoopingCall(self._poll_task, instance_id, task_ref, + loop = utils.LoopingCall(self._poll_task, instance_id, task_ref, done) - self.loop.start(FLAGS.vmwareapi_task_poll_interval, now=True) + loop.start(FLAGS.vmwareapi_task_poll_interval, now=True) ret_val = done.wait() - self.loop.stop() + loop.stop() return ret_val def _poll_task(self, instance_id, task_ref, done): -- cgit