VMware storage backend should use oslo.vmware
Currently, the VMware store is using its own copy of the VMwareApiSession to connect to vCenter server and ESX(i). This patch gets rid of this copy to use the oslo.vmware library. Closes-Bug: #1282715 Change-Id: I0aa47eada388c09d9835b00fb2c93f50f22675a4
This commit is contained in:
parent
3d5202df45
commit
18b4df178b
|
@ -1,273 +0,0 @@
|
|||
# Copyright (c) 2014 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.
|
||||
|
||||
"""
|
||||
Session and API call management for VMware ESX/VC server.
|
||||
Provides abstraction over glance.vmware.vim.Vim SOAP calls.
|
||||
"""
|
||||
|
||||
from eventlet import event
|
||||
|
||||
import glance.openstack.common.log as logging
|
||||
from glance.openstack.common import loopingcall
|
||||
from glance.store.vmware import error_util
|
||||
from glance.store.vmware import vim
|
||||
from glance.store.vmware import vim_util
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Retry(object):
|
||||
"""Decorator for retrying a function upon suggested exceptions.
|
||||
|
||||
The method retries for given number of times and the sleep
|
||||
time increments till the max sleep time is reached.
|
||||
If max retries is set to -1, then the decorated function is
|
||||
invoked indefinitely till no exception is thrown or if
|
||||
the caught exception is not in the list of suggested exceptions.
|
||||
"""
|
||||
|
||||
def __init__(self, max_retry_count=-1, inc_sleep_time=10,
|
||||
max_sleep_time=60, exceptions=()):
|
||||
"""Initialize retry object based on input params.
|
||||
|
||||
:param max_retry_count: Max number of times, a function must be
|
||||
retried when one of input 'exceptions'
|
||||
is caught. The default -1 will always
|
||||
retry the function till a non-exception
|
||||
case, or an un-wanted error case arises.
|
||||
:param inc_sleep_time: Incremental time in seconds for sleep time
|
||||
between retrial
|
||||
:param max_sleep_time: Max sleep time beyond which the sleep time will
|
||||
not be incremented using param inc_sleep_time
|
||||
and max_sleep_time will be used as sleep time
|
||||
:param exceptions: Suggested exceptions for which the function must be
|
||||
retried
|
||||
"""
|
||||
self._max_retry_count = max_retry_count
|
||||
self._inc_sleep_time = inc_sleep_time
|
||||
self._max_sleep_time = max_sleep_time
|
||||
self._exceptions = exceptions
|
||||
self._retry_count = 0
|
||||
self._sleep_time = 0
|
||||
|
||||
def __call__(self, f):
|
||||
|
||||
def _func(done, *args, **kwargs):
|
||||
try:
|
||||
result = f(*args, **kwargs)
|
||||
done.send(result)
|
||||
except self._exceptions as excep:
|
||||
LOG.exception(_("Failure while invoking function: "
|
||||
"%(func)s. Error: %(excep)s.") %
|
||||
{'func': f.__name__, 'excep': excep})
|
||||
if (self._max_retry_count != -1 and
|
||||
self._retry_count >= self._max_retry_count):
|
||||
done.send_exception(excep)
|
||||
else:
|
||||
self._retry_count += 1
|
||||
self._sleep_time += self._inc_sleep_time
|
||||
return self._sleep_time
|
||||
except Exception as excep:
|
||||
done.send_exception(excep)
|
||||
return 0
|
||||
|
||||
def func(*args, **kwargs):
|
||||
done = event.Event()
|
||||
loop = loopingcall.DynamicLoopingCall(_func, done, *args, **kwargs)
|
||||
loop.start(periodic_interval_max=self._max_sleep_time)
|
||||
result = done.wait()
|
||||
loop.stop()
|
||||
return result
|
||||
|
||||
return func
|
||||
|
||||
|
||||
class VMwareAPISession(object):
|
||||
"""Sets up a session with the server and handles all calls made to it."""
|
||||
|
||||
def __init__(self, server_ip, server_username, server_password,
|
||||
api_retry_count, task_poll_interval=5.0,
|
||||
scheme='https', create_session=True,
|
||||
wsdl_loc=None):
|
||||
"""Constructs session object.
|
||||
|
||||
:param server_ip: IP address of ESX/VC server
|
||||
:param server_username: Username of ESX/VC server admin user
|
||||
:param server_password: Password for param server_username
|
||||
:param api_retry_count: Number of times an API must be retried upon
|
||||
session/connection related errors
|
||||
:param scheme: http or https protocol
|
||||
:param create_session: Boolean whether to set up connection at the
|
||||
time of instance creation
|
||||
:param wsdl_loc: WSDL file location for invoking SOAP calls on server
|
||||
using suds
|
||||
"""
|
||||
self._server_ip = server_ip
|
||||
self._server_username = server_username
|
||||
self._server_password = server_password
|
||||
self._wsdl_loc = wsdl_loc
|
||||
self._api_retry_count = api_retry_count
|
||||
self._task_poll_interval = task_poll_interval
|
||||
self._scheme = scheme
|
||||
self._session_id = None
|
||||
self._vim = None
|
||||
if create_session:
|
||||
self.create_session()
|
||||
|
||||
@property
|
||||
def vim(self):
|
||||
if not self._vim:
|
||||
self._vim = vim.Vim(protocol=self._scheme, host=self._server_ip,
|
||||
wsdl_loc=self._wsdl_loc)
|
||||
return self._vim
|
||||
|
||||
@Retry(exceptions=(Exception))
|
||||
def create_session(self):
|
||||
"""Establish session with the server."""
|
||||
# Login and setup the session with the server for making
|
||||
# API calls
|
||||
session_manager = self.vim.service_content.sessionManager
|
||||
session = self.vim.Login(session_manager,
|
||||
userName=self._server_username,
|
||||
password=self._server_password)
|
||||
# Terminate the earlier session, if possible (For the sake of
|
||||
# preserving sessions as there is a limit to the number of
|
||||
# sessions we can have)
|
||||
if self._session_id:
|
||||
try:
|
||||
self.vim.TerminateSession(session_manager,
|
||||
sessionId=[self._session_id])
|
||||
except Exception as excep:
|
||||
# This exception is something we can live with. It is
|
||||
# just an extra caution on our side. The session may
|
||||
# have been cleared. We could have made a call to
|
||||
# SessionIsActive, but that is an overhead because we
|
||||
# anyway would have to call TerminateSession.
|
||||
LOG.exception(_("Error while terminating session: %s.") %
|
||||
excep)
|
||||
self._session_id = session.key
|
||||
LOG.info(_("Successfully established connection to the server."))
|
||||
|
||||
def __del__(self):
|
||||
"""Logs-out the session."""
|
||||
try:
|
||||
self.vim.Logout(self.vim.service_content.sessionManager)
|
||||
except Exception as excep:
|
||||
LOG.exception(_("Error while logging out the user: %s.") %
|
||||
excep)
|
||||
|
||||
def invoke_api(self, module, method, *args, **kwargs):
|
||||
"""Wrapper method for invoking APIs.
|
||||
|
||||
Here we retry the API calls for exceptions which may come because
|
||||
of session overload.
|
||||
|
||||
Make sure if a Vim instance is being passed here, this session's
|
||||
Vim (self.vim) instance is used, as we retry establishing session
|
||||
in case of session timedout.
|
||||
|
||||
:param module: Module invoking the VI SDK calls
|
||||
:param method: Method in the module that invokes the VI SDK call
|
||||
:param args: Arguments to the method
|
||||
:param kwargs: Keyword arguments to the method
|
||||
:return: Response of the API call
|
||||
"""
|
||||
|
||||
@Retry(max_retry_count=self._api_retry_count,
|
||||
exceptions=(error_util.VimException))
|
||||
def _invoke_api(module, method, *args, **kwargs):
|
||||
last_fault_list = []
|
||||
while True:
|
||||
try:
|
||||
api_method = getattr(module, method)
|
||||
return api_method(*args, **kwargs)
|
||||
except error_util.VimFaultException as excep:
|
||||
if error_util.NOT_AUTHENTICATED not in excep.fault_list:
|
||||
raise excep
|
||||
# If it is a not-authenticated fault, we re-authenticate
|
||||
# the user and retry the API invocation.
|
||||
|
||||
# Because of the idle session returning an empty
|
||||
# RetrieveProperties response and also the same is
|
||||
# returned when there is an empty answer to a query
|
||||
# (e.g. no VMs on the host), we have no way to
|
||||
# differentiate.
|
||||
# So if the previous response was also an empty
|
||||
# response and after creating a new session, we get
|
||||
# the same empty response, then we are sure of the
|
||||
# response being an empty response.
|
||||
if error_util.NOT_AUTHENTICATED in last_fault_list:
|
||||
return []
|
||||
last_fault_list = excep.fault_list
|
||||
LOG.warn(_("Not authenticated error occurred. "
|
||||
"Will create session and try "
|
||||
"API call again: %s.") % excep)
|
||||
self.create_session()
|
||||
|
||||
return _invoke_api(module, method, *args, **kwargs)
|
||||
|
||||
def _stop_loop(self, loop):
|
||||
loop.stop()
|
||||
|
||||
def wait_for_task(self, task):
|
||||
"""Return a deferred that will give the result of the given task.
|
||||
|
||||
The task is polled until it completes. The method returns the task
|
||||
information upon successful completion.
|
||||
|
||||
:param task: Managed object reference of the task
|
||||
:return: Task info upon successful completion of the task
|
||||
"""
|
||||
done = event.Event()
|
||||
loop = loopingcall.FixedIntervalLoopingCall(self._poll_task,
|
||||
task, done)
|
||||
loop.start(self._task_poll_interval)
|
||||
task_info = done.wait()
|
||||
loop.stop()
|
||||
return task_info
|
||||
|
||||
def _poll_task(self, task, done):
|
||||
"""Poll the given task.
|
||||
|
||||
If the task completes successfully then returns task info.
|
||||
In case of error sends back appropriate error.
|
||||
|
||||
:param task: Managed object reference of the task
|
||||
:param done: Event that captures task status
|
||||
"""
|
||||
try:
|
||||
task_info = self.invoke_api(vim_util, 'get_object_property',
|
||||
self.vim, task, 'info')
|
||||
if task_info.state in ['queued', 'running']:
|
||||
# If task already completed on server, it will not return
|
||||
# the progress.
|
||||
if hasattr(task_info, 'progress'):
|
||||
LOG.debug(_("Task: %(task)s progress: %(prog)s.") %
|
||||
{'task': task, 'prog': task_info.progress})
|
||||
return
|
||||
elif task_info.state == 'success':
|
||||
LOG.debug(_("Task %s status: success.") % task)
|
||||
done.send(task_info)
|
||||
else:
|
||||
error_msg = str(task_info.error.localizedMessage)
|
||||
LOG.exception(_("Task: %(task)s failed with error: %(err)s.") %
|
||||
{'task': task, 'err': error_msg})
|
||||
done.send_exception(error_util.VimFaultException([],
|
||||
error_msg))
|
||||
except Exception as excep:
|
||||
LOG.exception(_("Task: %(task)s failed with error: %(err)s.") %
|
||||
{'task': task, 'err': excep})
|
||||
done.send_exception(excep)
|
|
@ -1,48 +0,0 @@
|
|||
# Copyright (c) 2014 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.
|
||||
|
||||
"""
|
||||
Exception classes and SOAP response error checking module.
|
||||
"""
|
||||
|
||||
from glance.common import exception
|
||||
|
||||
|
||||
NOT_AUTHENTICATED = 'NotAuthenticated'
|
||||
|
||||
|
||||
class VimException(exception.GlanceException):
|
||||
"""The VIM Exception class."""
|
||||
|
||||
def __init__(self, msg):
|
||||
exception.GlanceException.__init__(self, msg)
|
||||
|
||||
|
||||
class SessionOverLoadException(VimException):
|
||||
"""Session Overload Exception."""
|
||||
pass
|
||||
|
||||
|
||||
class VimAttributeException(VimException):
|
||||
"""VI Attribute Error."""
|
||||
pass
|
||||
|
||||
|
||||
class VimFaultException(VimException):
|
||||
"""The VIM Fault exception class."""
|
||||
|
||||
def __init__(self, fault_list, msg):
|
||||
super(VimFaultException, self).__init__(msg)
|
||||
self.fault_list = fault_list
|
|
@ -1,241 +0,0 @@
|
|||
# Copyright (c) 2014 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 logging
|
||||
|
||||
import suds
|
||||
|
||||
from glance.store.vmware import error_util
|
||||
|
||||
logging.getLogger('suds').setLevel(logging.INFO)
|
||||
|
||||
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]
|
||||
:param wsdl_loc: Optional location of the VIM WSDL
|
||||
"""
|
||||
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, str(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."
|
|
@ -1,301 +0,0 @@
|
|||
# Copyright (c) 2014 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.
|
||||
|
||||
"""
|
||||
The VMware API utility module.
|
||||
"""
|
||||
|
||||
|
||||
def build_selection_spec(client_factory, name):
|
||||
"""Builds the selection spec.
|
||||
|
||||
:param client_factory: Factory to get API input specs
|
||||
:param name: Name for the selection spec
|
||||
:return: Selection spec
|
||||
"""
|
||||
sel_spec = client_factory.create('ns0:SelectionSpec')
|
||||
sel_spec.name = name
|
||||
return sel_spec
|
||||
|
||||
|
||||
def build_traversal_spec(client_factory, name, type, path, skip,
|
||||
select_set):
|
||||
"""Builds the traversal spec object.
|
||||
|
||||
:param client_factory: Factory to get API input specs
|
||||
:param name: Name for the traversal spec
|
||||
:param type: Type of the managed object reference
|
||||
:param path: Property path of the managed object reference
|
||||
:param skip: Whether or not to filter the object identified by param path
|
||||
:param select_set: Set of selection specs specifying additional objects
|
||||
to filter
|
||||
:return: Traversal spec
|
||||
"""
|
||||
traversal_spec = client_factory.create('ns0:TraversalSpec')
|
||||
traversal_spec.name = name
|
||||
traversal_spec.type = type
|
||||
traversal_spec.path = path
|
||||
traversal_spec.skip = skip
|
||||
traversal_spec.selectSet = select_set
|
||||
return traversal_spec
|
||||
|
||||
|
||||
def build_recursive_traversal_spec(client_factory):
|
||||
"""Builds Recursive Traversal Spec to traverse managed object hierarchy.
|
||||
|
||||
:param client_factory: Factory to get API input specs
|
||||
:return: Recursive traversal spec
|
||||
"""
|
||||
visit_folders_select_spec = build_selection_spec(client_factory,
|
||||
'visitFolders')
|
||||
# Next hop from Datacenter
|
||||
dc_to_hf = build_traversal_spec(client_factory, 'dc_to_hf', 'Datacenter',
|
||||
'hostFolder', False,
|
||||
[visit_folders_select_spec])
|
||||
dc_to_vmf = build_traversal_spec(client_factory, 'dc_to_vmf', 'Datacenter',
|
||||
'vmFolder', False,
|
||||
[visit_folders_select_spec])
|
||||
|
||||
# Next hop from HostSystem
|
||||
h_to_vm = build_traversal_spec(client_factory, 'h_to_vm', 'HostSystem',
|
||||
'vm', False,
|
||||
[visit_folders_select_spec])
|
||||
|
||||
# Next hop from ComputeResource
|
||||
cr_to_h = build_traversal_spec(client_factory, 'cr_to_h',
|
||||
'ComputeResource', 'host', False, [])
|
||||
cr_to_ds = build_traversal_spec(client_factory, 'cr_to_ds',
|
||||
'ComputeResource', 'datastore', False, [])
|
||||
|
||||
rp_to_rp_select_spec = build_selection_spec(client_factory, 'rp_to_rp')
|
||||
rp_to_vm_select_spec = build_selection_spec(client_factory, 'rp_to_vm')
|
||||
|
||||
cr_to_rp = build_traversal_spec(client_factory, 'cr_to_rp',
|
||||
'ComputeResource', 'resourcePool', False,
|
||||
[rp_to_rp_select_spec,
|
||||
rp_to_vm_select_spec])
|
||||
|
||||
# Next hop from ClusterComputeResource
|
||||
ccr_to_h = build_traversal_spec(client_factory, 'ccr_to_h',
|
||||
'ClusterComputeResource', 'host',
|
||||
False, [])
|
||||
ccr_to_ds = build_traversal_spec(client_factory, 'ccr_to_ds',
|
||||
'ClusterComputeResource', 'datastore',
|
||||
False, [])
|
||||
ccr_to_rp = build_traversal_spec(client_factory, 'ccr_to_rp',
|
||||
'ClusterComputeResource', 'resourcePool',
|
||||
False,
|
||||
[rp_to_rp_select_spec,
|
||||
rp_to_vm_select_spec])
|
||||
# Next hop from ResourcePool
|
||||
rp_to_rp = build_traversal_spec(client_factory, 'rp_to_rp', 'ResourcePool',
|
||||
'resourcePool', False,
|
||||
[rp_to_rp_select_spec,
|
||||
rp_to_vm_select_spec])
|
||||
rp_to_vm = build_traversal_spec(client_factory, 'rp_to_vm', 'ResourcePool',
|
||||
'vm', False,
|
||||
[rp_to_rp_select_spec,
|
||||
rp_to_vm_select_spec])
|
||||
|
||||
# Get the assorted traversal spec which takes care of the objects to
|
||||
# be searched for from the rootFolder
|
||||
traversal_spec = build_traversal_spec(client_factory, 'visitFolders',
|
||||
'Folder', 'childEntity', False,
|
||||
[visit_folders_select_spec,
|
||||
h_to_vm, dc_to_hf, dc_to_vmf,
|
||||
cr_to_ds, cr_to_h, cr_to_rp,
|
||||
ccr_to_h, ccr_to_ds, ccr_to_rp,
|
||||
rp_to_rp, rp_to_vm])
|
||||
return traversal_spec
|
||||
|
||||
|
||||
def build_property_spec(client_factory, type='VirtualMachine',
|
||||
properties_to_collect=None,
|
||||
all_properties=False):
|
||||
"""Builds the Property Spec.
|
||||
|
||||
:param client_factory: Factory to get API input specs
|
||||
:param type: Type of the managed object reference property
|
||||
:param properties_to_collect: Properties of the managed object reference
|
||||
to be collected while traversal filtering
|
||||
:param all_properties: Whether all the properties of managed object
|
||||
reference needs to be collected
|
||||
:return: Property spec
|
||||
"""
|
||||
if not properties_to_collect:
|
||||
properties_to_collect = ['name']
|
||||
|
||||
property_spec = client_factory.create('ns0:PropertySpec')
|
||||
property_spec.all = all_properties
|
||||
property_spec.pathSet = properties_to_collect
|
||||
property_spec.type = type
|
||||
return property_spec
|
||||
|
||||
|
||||
def build_object_spec(client_factory, root_folder, traversal_specs):
|
||||
"""Builds the object Spec.
|
||||
|
||||
:param client_factory: Factory to get API input specs
|
||||
:param root_folder: Root folder reference as the starting point for
|
||||
traversal
|
||||
:param traversal_specs: filter specs required for traversal
|
||||
:return: Object spec
|
||||
"""
|
||||
object_spec = client_factory.create('ns0:ObjectSpec')
|
||||
object_spec.obj = root_folder
|
||||
object_spec.skip = False
|
||||
object_spec.selectSet = traversal_specs
|
||||
return object_spec
|
||||
|
||||
|
||||
def build_property_filter_spec(client_factory, property_specs, object_specs):
|
||||
"""Builds the Property Filter Spec.
|
||||
|
||||
:param client_factory: Factory to get API input specs
|
||||
:param property_specs: Property specs to be collected for filtered objects
|
||||
:param object_specs: Object specs to identify objects to be filtered
|
||||
:return: Property filter spec
|
||||
"""
|
||||
property_filter_spec = client_factory.create('ns0:PropertyFilterSpec')
|
||||
property_filter_spec.propSet = property_specs
|
||||
property_filter_spec.objectSet = object_specs
|
||||
return property_filter_spec
|
||||
|
||||
|
||||
def get_objects(vim, type, max_objects, props_to_collect=None,
|
||||
all_properties=False):
|
||||
"""Gets all managed object references of a specified type.
|
||||
|
||||
It is caller's responsibility to continue or cancel retrieval.
|
||||
|
||||
:param vim: Vim object
|
||||
:param type: Type of the managed object reference
|
||||
:param max_objects: Maximum number of objects that should be returned in
|
||||
a single call
|
||||
:param props_to_collect: Properties of the managed object reference
|
||||
to be collected
|
||||
:param all_properties: Whether all properties of the managed object
|
||||
reference are to be collected
|
||||
:return: All managed object references of a specified type
|
||||
"""
|
||||
|
||||
if not props_to_collect:
|
||||
props_to_collect = ['name']
|
||||
|
||||
client_factory = vim.client.factory
|
||||
recur_trav_spec = build_recursive_traversal_spec(client_factory)
|
||||
object_spec = build_object_spec(client_factory,
|
||||
vim.service_content.rootFolder,
|
||||
[recur_trav_spec])
|
||||
property_spec = build_property_spec(client_factory, type=type,
|
||||
properties_to_collect=props_to_collect,
|
||||
all_properties=all_properties)
|
||||
property_filter_spec = build_property_filter_spec(client_factory,
|
||||
[property_spec],
|
||||
[object_spec])
|
||||
options = client_factory.create('ns0:RetrieveOptions')
|
||||
options.maxObjects = max_objects
|
||||
return vim.RetrievePropertiesEx(vim.service_content.propertyCollector,
|
||||
specSet=[property_filter_spec],
|
||||
options=options)
|
||||
|
||||
|
||||
def get_object_properties(vim, mobj, properties):
|
||||
"""Gets properties of the managed object specified.
|
||||
|
||||
:param vim: Vim object
|
||||
:param mobj: Reference to the managed object
|
||||
:param properties: Properties of the managed object reference
|
||||
to be retrieved
|
||||
:return: Properties of the managed object specified
|
||||
"""
|
||||
|
||||
client_factory = vim.client.factory
|
||||
if mobj is None:
|
||||
return None
|
||||
collector = vim.service_content.propertyCollector
|
||||
property_filter_spec = client_factory.create('ns0:PropertyFilterSpec')
|
||||
property_spec = client_factory.create('ns0:PropertySpec')
|
||||
property_spec.all = (properties is None or len(properties) == 0)
|
||||
property_spec.pathSet = properties
|
||||
property_spec.type = mobj._type
|
||||
object_spec = client_factory.create('ns0:ObjectSpec')
|
||||
object_spec.obj = mobj
|
||||
object_spec.skip = False
|
||||
property_filter_spec.propSet = [property_spec]
|
||||
property_filter_spec.objectSet = [object_spec]
|
||||
options = client_factory.create('ns0:RetrieveOptions')
|
||||
options.maxObjects = 1
|
||||
retrieve_result = vim.RetrievePropertiesEx(collector,
|
||||
specSet=[property_filter_spec],
|
||||
options=options)
|
||||
cancel_retrieval(vim, retrieve_result)
|
||||
return retrieve_result.objects
|
||||
|
||||
|
||||
def _get_token(retrieve_result):
|
||||
"""Get token from results to obtain next set of results.
|
||||
|
||||
:retrieve_result: Result from the RetrievePropertiesEx API
|
||||
:return: Token to obtain next set of results. None if no more results.
|
||||
"""
|
||||
return getattr(retrieve_result, 'token', None)
|
||||
|
||||
|
||||
def cancel_retrieval(vim, retrieve_result):
|
||||
"""Cancels the retrieve operation if necessary.
|
||||
|
||||
:param vim: Vim object
|
||||
:param retrieve_result: Result from the RetrievePropertiesEx API
|
||||
"""
|
||||
|
||||
token = _get_token(retrieve_result)
|
||||
if token:
|
||||
collector = vim.service_content.propertyCollector
|
||||
vim.CancelRetrievePropertiesEx(collector, token=token)
|
||||
|
||||
|
||||
def continue_retrieval(vim, retrieve_result):
|
||||
"""Continue retrieving results, if present.
|
||||
|
||||
:param vim: Vim object
|
||||
:param retrieve_result: Result from the RetrievePropertiesEx API
|
||||
"""
|
||||
|
||||
token = _get_token(retrieve_result)
|
||||
if token:
|
||||
collector = vim.service_content.propertyCollector
|
||||
return vim.ContinueRetrievePropertiesEx(collector, token=token)
|
||||
|
||||
|
||||
def get_object_property(vim, mobj, property_name):
|
||||
"""Gets property of the managed object specified.
|
||||
|
||||
:param vim: Vim object
|
||||
:param mobj: Reference to the managed object
|
||||
:param property_name: Name of the property to be retrieved
|
||||
:return: Property of the managed object specified
|
||||
"""
|
||||
props = get_object_properties(vim, mobj, [property_name])
|
||||
prop_val = None
|
||||
if props:
|
||||
prop = None
|
||||
if hasattr(props[0], 'propSet'):
|
||||
# propSet will be set only if the server provides value
|
||||
# for the field
|
||||
prop = props[0].propSet
|
||||
if prop:
|
||||
prop_val = prop[0].val
|
||||
return prop_val
|
|
@ -20,6 +20,7 @@ import httplib
|
|||
|
||||
import netaddr
|
||||
from oslo.config import cfg
|
||||
from oslo.vmware import api
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
from glance.common import exception
|
||||
|
@ -27,7 +28,7 @@ import glance.openstack.common.log as logging
|
|||
import glance.store
|
||||
import glance.store.base
|
||||
import glance.store.location
|
||||
from glance.store.vmware import api
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
@ -23,17 +23,21 @@ VMware Datastore backend
|
|||
|
||||
import ConfigParser
|
||||
import httplib
|
||||
import logging
|
||||
import os
|
||||
|
||||
import oslo.config.cfg
|
||||
from oslo.vmware import api
|
||||
import six.moves.urllib.parse as urlparse
|
||||
import testtools
|
||||
|
||||
from glance.store.vmware import api
|
||||
import glance.store.vmware_datastore
|
||||
import glance.tests.functional.store as store_tests
|
||||
|
||||
|
||||
logging.getLogger('suds').setLevel(logging.INFO)
|
||||
|
||||
|
||||
def read_config(path):
|
||||
cp = ConfigParser.RawConfigParser()
|
||||
cp.read(path)
|
||||
|
@ -47,6 +51,7 @@ def parse_config(config):
|
|||
'vmware_server_username',
|
||||
'vmware_server_password',
|
||||
'vmware_api_retry_count',
|
||||
'vmware_task_poll_interval',
|
||||
'vmware_store_image_dir',
|
||||
'vmware_datacenter_path',
|
||||
'vmware_datastore_name',
|
||||
|
@ -63,13 +68,14 @@ class VMwareDatastoreStoreError(RuntimeError):
|
|||
|
||||
|
||||
def vsphere_connect(server_ip, server_username, server_password,
|
||||
api_retry_count, scheme='https',
|
||||
create_session=True, wsdl_loc=None):
|
||||
api_retry_count, task_poll_interval,
|
||||
scheme='https', create_session=True, wsdl_loc=None):
|
||||
try:
|
||||
return api.VMwareAPISession(server_ip,
|
||||
server_username,
|
||||
server_password,
|
||||
api_retry_count,
|
||||
task_poll_interval,
|
||||
scheme=scheme,
|
||||
create_session=create_session,
|
||||
wsdl_loc=wsdl_loc)
|
||||
|
@ -105,6 +111,7 @@ class TestVMwareDatastoreStore(store_tests.BaseTestCase, testtools.TestCase):
|
|||
config['vmware_server_username'],
|
||||
config['vmware_server_password'],
|
||||
config['vmware_api_retry_count'],
|
||||
config['vmware_task_poll_interval'],
|
||||
scheme=scheme)
|
||||
|
||||
self.vmware_config = config
|
||||
|
|
|
@ -78,7 +78,7 @@ class FakeHTTPConnection(object):
|
|||
|
||||
class TestStore(base.StoreClearingUnitTest):
|
||||
|
||||
@mock.patch('glance.store.vmware.api.VMwareAPISession', autospec=True)
|
||||
@mock.patch('oslo.vmware.api.VMwareAPISession', autospec=True)
|
||||
def setUp(self, mock_session):
|
||||
"""Establish a clean test environment"""
|
||||
super(TestStore, self).setUp()
|
||||
|
|
|
@ -22,11 +22,13 @@ iso8601>=0.1.8
|
|||
ordereddict
|
||||
oslo.config>=1.2.0
|
||||
stevedore>=0.14
|
||||
suds>=0.4
|
||||
|
||||
# For Swift storage backend.
|
||||
python-swiftclient>=1.6
|
||||
|
||||
# For VMware storage backed.
|
||||
oslo.vmware
|
||||
|
||||
# For paste.util.template used in keystone.common.template
|
||||
Paste
|
||||
|
||||
|
|
Loading…
Reference in New Issue